Arjen Markus (23 november 2007) Plotchart is very useful for displaying data in plots or charts, but the usual use is rather static: the data come from "somewhere" and are then shown.
The program below shows that it is possible to use it in an interactive way too. The idea is that rather than typing in a lot of numbers in a table, it is easier to click in a window and to see the resulting data series in an xy-plot immediately.
Notes:
# editgraph.tcl -- # Widget to edit data series graphically # # Note: # Not as elegant as could be with bindings via Plotchart itself # package require Plotchart namespace eval ::Editgraph { variable graph variable endedit } # editSeries -- # Procedure to edit a series of data graphically # # Arguments: # xrange Range for the x values # yrange Range for the y values # number Number of data points # # Result: # List of x,y values representing the data series # # Note: # The widget allows setting new y values for given x values only # proc ::Editgraph::editSeries {xrange yrange number} { variable graph variable endedit toplevel .graph wm title .graph "Editing series" canvas .graph.c -width 400 -height 300 button .graph.ok -text OK -width 10 -command {::Editgraph::SaveSeries} button .graph.cancel -text Cancel -width 10 -command {::Editgraph::CancelSeries} grid .graph.c - grid .graph.ok .graph.cancel # # Initialise the series # foreach {xmin xmax} $xrange {break} foreach {ymin ymax} $yrange {break} if { $ymin <= 0.0 && $ymax >= 0.0 } { set y 0.0 } else { set y $ymin } set delx [expr {($xmax-$xmin)/double($number-1)}] set graph(xy) {} set x $xmin while { $x < $xmax+0.5*$delx } { lappend graph(xy) $x $y set x [expr {$x + $delx}] if { abs($x) < 0.5 *$delx } { set x 0.0 } } set graph(xmin) $xmin set graph(xmax) $xmax set graph(delx) $delx set graph(number) $number set graph(plot) [::Plotchart::createXYPlot .graph.c \ [::Plotchart::determineScale $xmin $xmax] \ [::Plotchart::determineScale $ymin $ymax]] $graph(plot) dataconfig data -colour blue set graph(widget) .graph.c foreach {x y} $graph(xy) { $graph(plot) plot data $x $y } # # Set the bindings # # bind .graph.c <Button-1> {::Editgraph::EditPoint %W %x %y} bind .graph.c <ButtonPress-1> { ::Editgraph::EditPoint %W %x %y bind %W <Motion> {::Editgraph::EditPoint %W %%x %%y} } bind .graph.c <ButtonRelease-1> { bind %W <Motion> {} } vwait ::Editgraph::endedit puts "$endedit" if { $endedit == 1 } { return $graph(xy) } else { return {} } } # SaveSeries, CancelSeries -- # Callback procedure for the widget # # Arguments: # None # # Result: # None # # Side effects: # The widget is closed # proc ::Editgraph::SaveSeries {} { set ::Editgraph::endedit 1 destroy .graph } proc ::Editgraph::CancelSeries {} { set ::Editgraph::endedit 0 destroy .graph } # EditPoint -- # Callback procedure for setting the new data point # # Arguments: # w Widget # x X-coordinate of selected point (pixel) # y Y-coordinate of selected point (pixel) # # Result: # None # # Side effects: # The data points are updated and the graph redrawn # proc ::Editgraph::EditPoint {w x y} { variable graph foreach {xc yc} [::Plotchart::pixelToCoords $graph(widget) $x $y] {break} set xp [expr {int(0.5+($xc-$graph(xmin))/$graph(delx))}] if { $xp < 0 } { set xp 0 } if { $xp > $graph(number) } { set xp $graph(number) } lset graph(xy) [expr {2*$xp+1}] $yc # # Reset the plotted data # $graph(widget) delete data $graph(plot) plot data "" "" # # Redraw the data # foreach {x y} $graph(xy) { $graph(plot) plot data $x $y } } # main -- # Make it all work catch { console show } set xyseries [::Editgraph::editSeries {0 100} {-50 50} 10] puts "Series:" foreach {x y} $xyseries { puts [format "%10.4f %10.4f" $x $y] }
aaaaaaaaa - 2009-06-17 12:33:32
I am getting this error. Does the Plotchart have undocumented dependencies?
invalid command name "console"
while executing
"console show"
(procedure "DrawXaxis" line 25) invoked from within
"DrawXaxis $w $xmin $xmax $xdelt"
(procedure "::Plotchart::createXYPlot" line 31) invoked from within
"::Plotchart::createXYPlot .graph.c ::Plotchart::determineScale $xmin $xmax ::Plotchart::determineScale $ymin $ymax"
(procedure "::Editgraph::editSeries" line 42) invoked from within
"::Editgraph::editSeries {0 100} {-50 50} 10"
invoked from within
"set xyseries ::Editgraph::editSeries {0 100} {-50 50} 10"
("uplevel" body line 1) invoked from within
"uplevel #0 {set xyseries ::Editgraph::editSeries {0 100} {-50 50} 10}"
MG The console show command is only available in wish on Windows, where the process isn't attached to a system console, and shows a pure-Tcl console with stdin/stdout redirected to it. Plotchart likely should't be calling it (or should at least catch it). You can most likely just define
proc console {args} {return}
or something like that so that the console show command does nothing, which should make your code run OK.
aaaaaaaaa - 2009-06-17 13:39:40
That makes sense. I was using tkcon.
I see the catch statement in the sample code above. However, it seems that "console" is being used internally by Plotchart too. Hope it will get fixed. Would a "package req Tk" eliminate the need for console all together?
MG I've never used Plotchart, so I'm not entirely sure what it does. But a quick glance at the docs suggests it's a Tk/graphical package, so I honestly don't know why it would need to use the console command at all. I can only assume it's either a mistake (left from debugging on Windows or something), or that it prints something to stdout at some point, and the console is intended to make sure that's visible on non-linux systems, and should be in a catch. But if you don't want to go sourcediving into the code for Plotchart, a no-op console command is likely the easiest way to avoid the error. (Would recommend reporting the error, too - I think tklib lives in tcllib's sf.net site?)
AM (18 june 2009) Hm, yes, that was a left-over - it should have gone from the source code in CVS, but not yet in the released version.