Version 1 of Use an xy-plot to edit data series

Updated 2009-06-17 14:18:23 by emiliano

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:

  • It does use some inside information on the workings of Plotchart, and I hope to come up with a simpler way to achieve the effects shown here shortly.
  • You need a recent version of Plotchart, as it requires support for "missing values"

 # 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]
 }