[Arjen Markus] (19 april 2010) I have been musing about extending [Plotchart] with a few more specialised chart types. The one I have realised so far could - a bit pretentiously - be called a SCADA display (''cf.'' http://en.wikipedia.org/wiki/SCADA). The code below has a few scars and bruises from the development process, but you will get the idea, I hope. ---- ====== # plotscada.tcl -- # Facilities for plotting "SCADA" displays: # - Schematic drawings of a factory for instance # and measured values in the various parts # - Telemetry systems # package require Tcl 8.5 package require Tk package require Plotchart namespace eval ::Plotchart { set methodProc(scada,scaling) ScadaScaling set methodProc(scada,axis) ScadaAxis set methodProc(scada,object) ScadaObject set methodProc(scada,plot) ScadaPlot set methodProc(scada,angular-scaling) ScadaAngularScaling } # createScada -- # Create a new command for plotting SCADA displays # # Arguments: # w Name of the canvas # Result: # Name of a new command # Note: # The entire canvas will be dedicated to the display # proc ::Plotchart::createScada { w } { variable scada_scaling set newchart "scada_$w" interp alias {} $newchart {} ::Plotchart::PlotHandler scada $w #CopyConfig scada $w set pxmin 0 set pxmax [expr {[WidthCanvas $w] - 1}] set pymin [expr {[HeightCanvas $w] - 1}] set pymax 0 # Default scaling: use pixels - not an error ScadaScaling $w default [list $pxmin $pymax $pxmax $pymin] \ [list $pxmin $pymin $pxmax $pymax] return $newchart } # ScadaScaling -- # Create a new scaling for the SCADA display # # Arguments: # w Name of the canvas # name Name of the scaling # rectangle Rectangle in the window (pixels) # worldcoords Associated world coordinates # Result: # None # Note: # Order of the pixel coordinates: xmin, ymin, xmax, ymax # Ditto for the world coordinates, but the pixel y-coordinate # is actually inverted # proc ::Plotchart::ScadaScaling { w name rectangle worldcoords } { viewPort $w,$name {*}$rectangle worldCoordinates $w,$name {*}$worldcoords } # ScadaObject -- # Create a new object in the SCADA display # # Arguments: # w Name of the canvas # type Type of the object # objname Name of the object # coords List of coordinates # args List of all remaining arguments # Result: # None # Side effects: # A new object is created on the canvas and its properties are stored # proc ::Plotchart::ScadaObject { w type objname coords args} { variable scada_scaling variable scada_object set scada_object($w,$objname,scaling) default set options {} foreach {key value} $args { switch -- $key { "-scaling" { set scada_object($w,$objname,scaling) $value } "-text" { set scada_object($w,$objname,text) $value } default { lappend options $key $value } } } switch -- $type { "rectangle" { set scada_object($w,$objname,canvasid) \ [$w create rectangle {-10 -10 -10 -10} {*}$options] } "line" { set scada_object($w,$objname,canvasid) \ [$w create line {-10 -10 -10 -10} {*}$options] } "polygon" { set scada_object($w,$objname,canvasid) \ [$w create polygon {-10 -10 -10 -10 -10 -10} {*}$options] } "text" { set scada_object($w,$objname,canvasid) \ [$w create text {-10 -10} -text $scada_object($w,$objname,text) {*}$options] } default { return -code error "Unknown object type: $type" } } set scada_object($w,$objname,coords) $coords set scada_object($w,$objname,type) $type } # ScadaPlot -- # Change the properties (coordinates or text) of a SCADA object # # Arguments: # w Name of the canvas # objname Name of the object # args List of the parameters that need to be changed # Result: # None # Side effects: # The coordinates or the text are changed # proc ::Plotchart::ScadaPlot { w objname args} { variable scada_scaling variable scada_object set wcoords $scada_object($w,$objname,coords) set replace 0 set index 0 foreach coord $wcoords { if { $coord eq "*" } { lset wcoords $index [lindex $args $replace] incr replace } incr index } set scaling $w,$scada_object($w,$objname,scaling) set coords {} foreach {x y} $wcoords { if {[catch { foreach {px py} [coordsToPixel $scaling $x $y] {break} lappend coords $px $py } msg] } { puts "problem: $scaling -- $x -- $y" } } $w coords $scada_object($w,$objname,canvasid) $coords if { $scada_object($w,$objname,type) eq "text" } { if { $scada_object($w,$objname,text) eq "*" } { set text [lindex $args end] $w itemconfig $scada_object($w,$objname,canvasid) -text $text } } } # main -- # Simple test: # - Display a stirred vessel # - Flow rate of two components can be regulated via the scale widgets # - The reaction in the vessel is modelled with the "chemical" equation: # # A + B --> C + heat # # - Nothing realistic, just playing around with the possibilities # # Display a simple vessel # pack [canvas .c -height 300 -width 600] set p [::Plotchart::createScada .c] scale .c.scalea -orient vertical -from 0.10 -to 0.0 -variable ratea -tickinterval 0.02 -digits 3 -resolution 0.001 scale .c.scaleb -orient vertical -from 0.10 -to 0.0 -variable rateb -tickinterval 0.02 -digits 3 -resolution 0.001 button .c.start -text Start -command {startComputation} -width 10 button .c.stop -text Stop -command {stopComputation} -width 10 .c create window 10 30 -window .c.scalea -anchor nw .c create window 110 70 -window .c.scaleb -anchor nw .c create window 10 200 -window .c.start -anchor nw .c create window 110 200 -window .c.stop -anchor nw .c create line { 20 40 230 40} -width 2 -arrow last -fill red .c create text 230 30 -text "Component A" -anchor e -fill red .c create line {200 70 230 70} -width 2 -arrow last -fill blue .c create text 230 60 -text "Component B" -anchor e -fill blue .c create line {325 210 360 210} -width 2 -arrow last .c create text 340 200 -text "Product C" -anchor w .c create polygon {240 10 240 10 240 240 240 240 280 260 320 240 320 240 320 10 320 10} \ -fill orange -smooth 1 -outline black -width 2 .c create line {280 0 280 200} -width 2 .c create line {270 200 290 200} -width 2 .c create rectangle {250 190 270 210} -width 2 -fill black .c create rectangle {290 190 310 210} -width 2 -fill black $p scaling temperature {400 22 428 148} {0.0 10.0 1.0 100.0} .c create rectangle {400 20 430 150} -width 2 -fill white -outline black $p object rectangle thermometer {0.0 10.0 1.0 *} -scaling temperature -fill red $p object text temperature {1.3 *} -text * -scaling temperature -anchor w $p scaling concentration {480 102 510 248} {0.0 0.0 1.0 1.0} .c create rectangle {480 100 510 250} -width 2 -fill yellow -outline black $p object rectangle conca { 0.0 0.0 1.0 *} -fill red -scaling concentration $p object rectangle concb { 0.0 * 1.0 *} -fill blue -scaling concentration $p object text labela {1.3 *} -text A -scaling concentration $p object text labelb {1.3 *} -text B -scaling concentration $p object text labelc {1.3 *} -text C -scaling concentration $p object line maxline {-0.1 * 1.4 *} -scaling concentration $p object text maximum { 1.4 *} -text Maximum -scaling concentration -anchor w $p plot maxline 0.25 0.25 $p plot maximum 0.25 $p plot labelc 1.0 $p plot thermometer 25.0 $p plot temperature 25.0 "25" $p plot conca 0.1 $p plot concb 0.1 0.3 $p plot labela 0.1 $p plot labelb 0.3 # # Computational and GUI control part # # nextStep -- # Compute the values at the next step # # Arguments: # time Current time (not used) # values Current values # deltt Time step # # Result: # Values at the new time level # proc nextStep {time values deltt} { global k h alpha qa qb conca0 concb0 rcp temp0 foreach {conca concb concc temp} $values {break} set rateab [expr {$k * exp( $alpha * $temp ) * $conca * $concb}] set dva [expr {-$rateab + $qa * $conca0 - ($qa+$qb) * $conca}] set dvb [expr {-$rateab + $qb * $concb0 - ($qa+$qb) * $concb}] set dvc [expr {$rateab - ($qa+$qb) * $concc}] set dvt [expr {($h * $rateab + ($qa+$qb) * ($temp0 - $temp))/$rcp}] return [list \ [expr {$conca + $dva * $deltt}] \ [expr {$concb + $dvb * $deltt}] \ [expr {$concc + $dvc * $deltt}] \ [expr {$temp + $dvt * $deltt}]] } # # Set the coefficients # set k 1.0e-2 set alpha 0.1 set ratea 0.02 set rateb 0.02 set conca0 10 set concb0 10 set temp0 20 set h 3.0e5 set rcp 4.2e6 set conca 0.0 set concb 0.0 set concc 0.0 set temp 20.0 set time 0.0 set deltt 0.001 set values [list $conca $concb $concc $temp] proc startComputation {} { set ::stop 0 set ::qa $::ratea set ::qb $::rateb nextTime $::p $::time $::values $::deltt } proc stopComputation {} { set ::stop 1 } proc nextTime {display time values deltt} { if { $::stop } { return } foreach {conca concb concc temp} $values {break} $display plot thermometer $temp $display plot temperature $temp [format %5.1f $temp] set total [expr {$conca+$concb+$concc}] if { $total != 0.0 } { $display plot conca [expr {$conca/$total}] $display plot concb [expr {$conca/$total}] [expr {($conca+$concb)/$total}] } if { $temp > 100.0 } { tk_messageBox -type ok -message "Temperature is over 100 degrees!\nHalting computation" return } for {set i 0} {$i < 10000} {incr i} { set time [expr {$time + $deltt}] set values [nextStep $time $values $deltt] } after 1 [list nextTime $display $time $values $deltt] } ====== <>Category Plotting