Thanks for your interest. I love Tcl/Tk and have done several interesting projects in it.

My involvement with the language has gone through phases: late 1990's, break, mid-to-late 2000's, break, and now back for a third time. Fortunately, the language has stayed pretty simple and mostly the same over the years so the returns were not as hectic as it could have been with other languages.

My first involvement was for solving Traveling Salesman Problem ( TSP ). My idea was to start with a convex hull and iteratively solve the remainder of the points on the map. This was a novel approach at the time (based on the survey I did then). There seems to be more researchers doing similar things these days. I first programmed it in C, got some promising results. In one instance, I even got a solution that was better than the one published by the above website as the optimal path. However, it was difficult to see where the algorithm was going astray. Enter Tcl/TK: After seeing a fellow student with Prof O's book, I quickly rewrote the app in Tcl. I could see the map visually, and watch the algorithm doing its thing step by step. Awesome! Unfortunately I had to leave it as oher things came up.


In my second return, I developed a data flow language for non-technical users so they could get their jobs done without needing IT too much, or almost none at all as it came to be :-) . This app had its success in its own limited way. There were boxes that each performed some function, and by connecting these boxes together, one could accomplish certain tasks. This was quite easy to use and powerful at the same time.

saito df graph

Currently, I am back and in the final stages of a more modern, more complete and more capable version of that data flow app. It is designed to be a data engineering platform for anyone who deals with data in one way or another. It has some quite advanced features: in its core functionality, as an app in its configuration and maintenance, and in its extra features. I can write about those later.

saito new df


Some technical info:

The data flow application above is an implementation of a directed acyclic graph or a DAG (DAG ). Basically, there are nodes and edges, represented as boxes and arrows. respectively. The edges are directional, meaning that an edge goes from one node to another, and it does not automatically imply that an edge with the reverse direction is also present. The graph is acyclic meaning that there are no cycles regardless of what sequence of edges you take to traverse through the graph.

An edge or arrow connects a node to another via its ports. A port is a construct that allows data to be transferred along the edges. As such, from a node's point of view, there are input ports where data comes in, and output ports where data leaves the node. In between the two ports, the node performs its action, whatever that may be.

A node implements a unit of functionality. By connecting them together in a network via various arrows one implements a higher-level business objective or a goal, or more familiarly a program. DAG implementations are usually domain specific.

While this is a mathematical concept in essence, this abstraction is quite useful in Computer Science and it has a number of applications.

The implementation above is driven purely by data, stored in a database. I originally developed it to process data in an Oracle database. It has of course grown to support other databases, including SQLite.

To be continued...

saito - 2022-12-25

Convex_Hull contains code to compute the convex hull of a given set of points


A small fun project:

canvas art work

saito - 2023-02-17

Do you miss the little snipping utility from Windows 10? MS has removed it from Windows 11. But no worries...


saito - 2023-03-10

Create various shapes on canvas:

canvas shapes

saito - 2023-05-10

Demo time:


saito - 2024-03-15

I have this little app that shows the time, plus it has a chronometer. I have used it for a while now and it was surprising to me how useful this little addition has made it. Options are not currently saved, but one could easily fill that gap.

So, I am sharing it here if anyone else finds it useful. chronotimer

Here is the code:

package require Tk

## schedule the first run
after 40 [list chronoStart 990 0]

proc getElapsedTime {seconds1 seconds2} {

     set eTime   [expr {abs($seconds2 - $seconds1)}]
     set seconds [expr {$eTime % 60}]

     set eTime   [expr {$eTime / 60}]
     set minutes [expr {$eTime % 60}]

     set eTime   [expr {$eTime / 60}]
     set hours   $eTime

     if {$hours == 0} {
          ## [format "%dmin %dsec" $minutes $seconds]
          set result [format "%d:%02d" $minutes $seconds]
     } else {
          ## [format "%dhr %dmin %dsec" $hours $minutes $seconds]
          set result [format "%d:%02d:%02d" $hours $minutes $seconds]

     return $result

proc updateTimer {currWin elapseWin} {
     global opts

     if {$opts(inMotion)} {
               after $opts(refreshMS) [list updateTimer $currWin $elapseWin]

     set currTime   [clock seconds]
     if {$opts(showMilli)} {
               set milliSecs ".[string range [clock milli] end-2 end]"
     } else {
               set milliSecs ""

     if {$opts(use24)} {
          set now [clock format $currTime -format {%H:%M:%S}]
     } else {
          set now [clock format $currTime -format {%l:%M:%S}]
          set now [string trim  $now]
     $currWin   configure -text " ${now}${milliSecs} "
     $elapseWin configure -text [getElapsedTime $currTime $opts(startSecs)]

     after $opts(refreshMS) [list updateTimer $currWin $elapseWin]

proc chronoStart {{refreshMS 50} {showMilliSecs 1}} {
     global opts

     ## colors
     set opts(alpha)       100
     set opts(bgColor)     #ffffb9
     set opts(fgColor1)    #ff8000
     set opts(fgColor2)    #0080ff

     ## fonts
     ## {Consolas 24 bold}
     ## {Consolas 36 bold}
     ## {Ravie 36 bold}
     ## {{Comic Sans MS} 36 bold}
     ## {Ravie 24 bold}
     ## {{Comic Sans MS} 24 bold}
     set opts(fontCurr)       {Consolas 39 bold}
     set opts(fontElapsed) {Consolas 24 bold}

     ## controls
     set opts(use24)       1
     set opts(refreshMS)   $refreshMS
     set opts(showMilli)   $showMilliSecs
     set opts(startSecs)   [clock seconds]

     set top    .
     wm title   $top "Chrono-Timer"
     wm minsize $top  360 130
     wm resizable $top 0 0

     set frWin .show
     set optFr .opts

     $top config -bg $opts(bgColor)
     frame $frWin -bd 2 -bg $opts(bgColor)
     frame $optFr -bd 2

     wm attributes $top -alpha [expr {$opts(alpha) / 100.}]

     label $frWin.lCurrTime -font       $opts(fontCurr)                \
                                 -background $opts(bgColor)                \
                            -foreground $opts(fgColor1)
     label $frWin.lElapsed  -font       $opts(fontElapsed)        \
                                 -background $opts(bgColor)                \
                            -foreground $opts(fgColor2)

     grid $frWin.lCurrTime -row 1 -column 1 -sticky news -columnspan 3
     grid $frWin.lElapsed  -row 2 -column 3 -sticky news

     grid rowconfigure    $frWin 1 -weight 1
     grid rowconfigure    $frWin 2 -weight 1

     grid columnconfigure $frWin 1 -weight 1
     grid columnconfigure $frWin 2 -weight 1
     grid columnconfigure $frWin 3 -weight 1

     grid $frWin -row 1 -column 1 -sticky news
     grid $optFr -row 2 -column 1 -sticky news

     grid rowconfigure    $top 1 -weight 0
     grid rowconfigure    $top 2 -weight 1

     grid columnconfigure $top 1 -weight 1

     grid forget $optFr

     label $optFr.l1 -text "Display" -anchor e
     radiobutton $optFr.showms -text "Show milli"                \
                              -anchor w                                \
                              -width 10                                \
                              -highlightthickness 0                \
                              -value 1                                \
                              -variable opts(showMilli)
     radiobutton $optFr.hidems -text "Hide milli"                \
                              -anchor w                                \
                              -width 10                                \
                              -highlightthickness 0                \
                              -value 0                                \
                              -variable opts(showMilli)
     checkbutton $optFr.use24 -text "Use Military Time"                \
                              -anchor w                                \
                              -width 10                                \
                                   -justify right                        \
                              -variable opts(use24)

     label  $optFr.l2 -text "Time" -anchor e
     button $optFr.b2 -text "Change Color"                        \
                           -width 12 -command [list chronoChangeColor fgColor1]

     label  $optFr.l3 -text "Chrono" -anchor e
     button $optFr.b3 -text "Change Color"                        \
                           -width 12 -command [list chronoChangeColor fgColor2]

     label  $optFr.l4 -text "Background" -anchor e
     button $optFr.b4 -text "Change Color"                        \
                           -width 12 -command [list chronoChangeColor bgColor]

     label $optFr.l5 -text "Refresh (msecs)" -anchor e
     entry $optFr.e5 -width 5 -textvar opts(refreshMS2)

     label $optFr.l6 -text "Alpha %" -anchor se
     scale $optFr.alpha -from 10 -to 100 -showvalue 1 -bd 2                  \
                     -sliderrelief ridge -resolution 5 -orient horizontal \
                     -variable opts(alpha)

     set bFr $optFr.bFr
     frame $bFr -bd 0  -relief flat
     button $  -text "Save"        \
                       -state disabled        \
                            -width 6 -command [list chronoSaveOpts      $top]
     button $bFr.reset -text "Reset"        \
                            -width 6 -command [list chronoResetTimer    $top]
     button $bFr.hide  -text "Hide"        \
                            -width 6 -command [list chronoToggleDisplay $top]
     button $bFr.quit  -text "Quit"        \
                       -fg red                \
                            -width 6 -command [list chronoQuit          $top]

     grid $  -row 1 -column 1 -sticky news -padx 4p
     grid $bFr.reset -row 1 -column 2 -sticky news -padx 4p
     grid $bFr.hide  -row 1 -column 3 -sticky news -padx 4p
     grid $bFr.quit  -row 1 -column 4 -sticky news -padx 4p

     grid rowconfigure    $bFr 1 -weight 0

     grid columnconfigure $bFr 1 -weight 1
     grid columnconfigure $bFr 2 -weight 1
     grid columnconfigure $bFr 3 -weight 1
     grid columnconfigure $bFr 4 -weight 1

     grid $optFr.l1     -row 1 -column 1 -sticky news -padx 2p
     grid $optFr.showms -row 1 -column 2 -sticky news -padx 2p
     grid $optFr.hidems -row 2 -column 2 -sticky news -padx 2p
     grid $optFr.use24  -row 3 -column 2 -sticky news -padx 2p -pady 2p

     grid $optFr.l2 -row 4 -column 1 -sticky news -pady 4 -padx 2p
     grid $optFr.b2 -row 4 -column 2 -sticky news -pady 4 -padx 2p

     grid $optFr.l3 -row 5 -column 1 -sticky news -pady 4 -padx 2p
     grid $optFr.b3 -row 5 -column 2 -sticky news -pady 4 -padx 2p

     grid $optFr.l4 -row 6 -column 1 -sticky news -pady 4 -padx 2p
     grid $optFr.b4 -row 6 -column 2 -sticky news -pady 4 -padx 2p

     grid $optFr.l5 -row 7 -column 1 -sticky news -pady 4 -padx 2p
     grid $optFr.e5 -row 7 -column 2 -sticky news -pady 4 -padx 2p

     grid $optFr.l6    -row 8 -column 1 -sticky news -pady 2 -padx 2p
     grid $optFr.alpha -row 8 -column 2 -sticky news -pady 2 -padx 2p

     grid $optFr.bFr -row 9 -column 1 -sticky ews -pady {10p 4p} -columnspan 3

     grid rowconfigure    $optFr 1 -weight 0
     grid rowconfigure    $optFr 2 -weight 0
     grid rowconfigure    $optFr 3 -weight 0
     grid rowconfigure    $optFr 4 -weight 0
     grid rowconfigure    $optFr 5 -weight 0
     grid rowconfigure    $optFr 6 -weight 0
     grid rowconfigure    $optFr 7 -weight 0
     grid rowconfigure    $optFr 8 -weight 1

     grid columnconfigure $optFr 1 -weight 1
     grid columnconfigure $optFr 2 -weight 1
     grid columnconfigure $optFr 3 -weight 1

     bind $frWin           <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $frWin.lCurrTime <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $frWin.lElapsed  <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $optFr           <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $optFr.l1        <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $optFr.l2        <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $optFr.l3        <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $optFr.l4        <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $optFr.l5        <ButtonRelease-1> [list chronoToggleDisplay $top]
     bind $optFr.l6        <ButtonRelease-1> [list chronoToggleDisplay $top]

     bind $top             <Button-1>  [list chronoStartMotion     %W %x %y]
     bind $frWin           <B1-Motion> [list chronoMoveTimerWindow %W %x %y]
     bind $frWin.lCurrTime <B1-Motion> [list chronoMoveTimerWindow %W %x %y]
     bind $frWin.lElapsed  <B1-Motion> [list chronoMoveTimerWindow %W %x %y]

     set opts(inMotion)    0
     set opts(cfgOpen)     0
     set opts(timeFr)     $frWin
     set opts(optFr)      $optFr
     set opts(timeWin)    $frWin.lCurrTime
     set opts(chronWin)   $frWin.lElapsed
     set opts(refreshMS2) $opts(refreshMS)

     ## start
     updateTimer $frWin.lCurrTime $frWin.lElapsed

proc chronoSaveOpts {top} {}

proc chronoQuit {top} {exit}

proc getWindowWidth {win} {
     lassign  [split [winfo geometry $win] "x+"] origW origH origX origY
     return $origW

proc getWindowHeight {win} {
     lassign  [split [winfo geometry $win] "x+"] origW origH origX origY
     return $origH

proc chronoStartMotion {win x y} {
     global opts

     set opts(xOffSet) $x
     set opts(yOffSet) $y

proc chronoMoveTimerWindow {win x y} {
     global opts

     set opts(inMotion) 1

     set topWin [winfo toplevel $win]
     lassign    [split [winfo geometry $topWin] "+"] origDims origX origY

     set newX   [expr {$origX + $x}]
     set newY   [expr {$origY + $y}]

     set newX   [expr {$newX - $opts(xOffSet)}]
     set newY   [expr {$newY - $opts(yOffSet)}]

     wm geometry $topWin "${origDims}+${newX}+${newY}"

proc chronoResetTimer {top} {
     global opts

     set opts(startSecs)   [clock seconds]

     chronoToggleDisplay $top

proc chronoToggleDisplay {top} {
     global opts

     if {$opts(inMotion)} {
               set opts(inMotion) 0

     set opts(refreshMS2) [string trim $opts(refreshMS2)]
     if {[string is integer -strict $opts(refreshMS2)]} {
               set opts(refreshMS) $opts(refreshMS2)
     set opts(refreshMS2) $opts(refreshMS)

     if {$opts(cfgOpen)} {
          set opts(cfgOpen) 0
               grid forget $opts(optFr)

          ## resize back to timer window
          wm resizable $top 0 0
          wm minsize $top [getWindowWidth  $opts(timeFr)] \
                            [getWindowHeight $opts(timeFr)] 
          wm minsize $top 360 130
          wm maxsize $top [getWindowWidth  $opts(timeFr)] \
                            [getWindowHeight $opts(timeFr)]

     } else {
          set opts(cfgOpen) 1
               grid $opts(optFr) -row 2 -column 1 -sticky news

          ## make sure config opts are visible
           wm resizable $top 1 1 
          wm minsize   $top 360 500
          wm maxsize   $top [winfo screenwidth .] [winfo screenheight .]

     wm attributes $top -alpha [expr {$opts(alpha) / 100.}]

proc chronoChangeColor {opt} {
     global opts

     set new [tk_chooseColor -initialcolor $opts($opt)        -title "Choose Color" ]
     if {$new ne ""} {
               set opts($opt) $new

     switch $opt {
             fgColor1 { $opts(timeWin)  config -background $opts(bgColor)        \
                                          -foreground $opts(fgColor1)
        fgColor2 { $opts(chronWin) config -background $opts(bgColor)        \
                                          -foreground $opts(fgColor2)
        bgColor  { $opts(timeFr)   config -bg $opts(bgColor)
                   $opts(timeWin)  config -bg $opts(bgColor)
                   $opts(chronWin) config -bg $opts(bgColor)