Simple data plotting example

WikiDbImage sample.gif

Arjen Markus (19 july 2002) I have wanted something like this for a long time: a simple little program that can plot the data from a simple file. This is my solution:

  • Use emu_graph by Steve Casssidy [L1 ] to do the hard work
  • Use a set of simple routines to read the file in question (make that customisable!)
  • Put it together in a small script that takes care of the more general tasks.

Note: If the script file "custom.tcl" is present in the working directory, then this will be sourced, for instance to replace the general but simple procs, readNames and readData.

Note: The user-interface is not very pretty, it is effective though.


Sample data file:

 time temp rain wind vaporp
  1    46     1    27    73
  2    41     3    32    71
  3    42     0    31    72
  4    39     0    32    71
  5    37     0    39    66
  6    35     1    44    66
  7    33     3    42    67
  8    33     1    43    67
  9    32     2    43    68
 10    35     1    41    66
 11    27    11    31    65
 12    22     9    25    63
 13    19     1    24    61
 14    15     0    28    61
 15    16     1    22    60
 16    19     0    26    56
 17    16     0    49    46
 18    10     0    49    40
 19    10     0    51    37
 20     4     0    43    39
 21     3     0    40    37

The script:

 # simplegraph.tcl --
 #    Script to read and display timeseries
 #
 # Notes:
 #    This script defines a number of procs that can be overridden
 #    via a customised script file in the working directory:
 #    - openFile   - to open a file and prepare for reading
 #    - readNames  - to read the names of the series
 #    - readData   - to read the data for the series
 #    This is done specifically to make it possible to read (almost)
 #    any file without touching the script itself.
 #    The script file should be called "custom.tcl"
 #    The main script defines default procs that should be effective
 #    for simple cases.

 # openFile --
 #    Open the file and start looking for data
 #
 # Arguments:
 #    filename    Name of the file to read
 #
 # Result:
 #    infile      Handle to the opened file
 #
 # Side effects:
 #    A file in question is opened. Lines containing a * or # as
 #    the first non-blank character are considered comments. The
 #    first line without this is considered to be a line with the
 #    names of the columns.
 #
 proc openFile {filename} {
    set infile [open $filename "r"]
    return $infile
 }

 # readNames --
 #    Read the names of the columns
 #
 # Arguments:
 #    infile      Handle to the file
 #
 # Result:
 #    names       List of names, the number indicates the number of
 #                columns
 #
 proc readNames {infile} {
    #
    # Skip the header - if any
    #
    set pos    0
    while { [gets $infile line] >= 0 } {
       if { [regexp {[ \t]*[*#]} $line] } {
          incr pos
       } else {
          break
       }
    }
    seek $infile 0 start
    while { $pos > 0 } {
       gets $infile line
       incr pos -1
    }

    #
    # Read the line with the column names
    #
    gets $infile line

    # Force the line to be interpreted as a list
    set nocols [llength $line]

    return $line
 }

 # readData --
 #    Read the data per line
 #
 # Arguments:
 #    infile      Handle to the file
 #
 # Result:
 #    values      List of values, representing each column.
 #                A list of length zero indicates the end of file
 #
 proc readData {infile} {
    while { [gets $infile values] == 0 } { ;# Just go on - skip empty lines }

    set nocols [llength $values]

    return $values
 }

 # readFile --
 #    Read the file and store the data in a (global) array
 #
 # Arguments:
 #    filename    Name of the file
 #
 # Result:
 #    None
 #
 # Side effects:
 #    Filled array, ready for display
 #
 proc readFile {filename} {
    global data_array

    set infile [openFile $filename]

    set data_array(names) [readNames $infile]
    set i 0
    foreach name $data_array(names) {
       set data_array($i) {}
       incr i
    }

    while 1 {
       set values [readData $infile]
       if { [llength $values] > 0 } {
          set i 0
          foreach value $values {
             lappend data_array($i) $value
             incr i
          }
       } else {
          break
       }
    }
 }

 # makePlotData --
 #    Make a list useable by emu_graph
 #
 # Arguments:
 #    xindex     Index of x data
 #    yindex     Index of y data
 #
 # Result:
 #    None
 #
 # Side effects:
 #    A dataset for emu_graph
 #
 proc makePlotData {xindex yindex} {
    global data_array

    set xydata {}
    foreach x $data_array($xindex) y $data_array($yindex) {
       lappend xydata $x $y
    }

    plot data xydata -colour black -lines 1 -points 0 -coords $xydata
 }

 # updatePlot --
 #    Update the plot data
 #
 # Arguments:
 #    name       Name of the variable
 #    op         Operation (both ignored)
 #
 # Result:
 #    None
 #
 # Side effects:
 #    New plot data for the graph
 #
 proc updatePlot {name op} {
    global data_array

    set xindex [lsearch $data_array(names) $::xname]
    set yindex [lsearch $data_array(names) $::yname]
    makePlotData $xindex $yindex
 }

 # mainWindow --
 #    Create the main window
 #
 # Arguments:
 #    None
 #
 # Result:
 #    None
 #
 # Side effects:
 #    A toplevel window with a canvas and some buttons
 #
 proc mainWindow {} {
    global data_array

    frame  .frm
    eval tk_optionMenu .frm.x xname $data_array(names)
    eval tk_optionMenu .frm.y yname $data_array(names)
    set ::xname [lindex $data_array(names) 0]
    set ::yname [lindex $data_array(names) 1]

    .frm.x configure -width 10
    .frm.y configure -width 10

    trace add variable ::xname write {after 200 updatePlot}
    trace add variable ::yname write {after 200 updatePlot}

    pack .frm.x .frm.y -side top

    canvas .cnv -width 600 -height 400 -background lightgrey
    button .exit -text "Exit" -command "exit"
    pack .frm .cnv  -side left
    pack .exit -side top
    emu_graph plot -canvas .cnv -width 400 -height 300
 }

 # main --
 #    Main code, controlling the program
 #

 global data_array

 if { [file exists "custom.tcl"] } {
    source "custom.tcl"
 }
 readFile [lindex $::argv 0]

 #
 # Get emu_graph
 #
 source "graph.tcl"

 mainWindow
 makePlotData 0 1

KBK Note that there's an example that just scribbles a graph in the canvas: Shuffle a list: graph results. It shows how readily a "quick-and-dirty" plot can be done, even with nothing but Tcl/Tk!