## How to plot a graph

David Bigelow wrote in comp.lang.tcl:

The thing to remember with the Tcl Canvas widget is that it starts its 0 0 at the upper left corner of your display and works down. Knowing that you are looking to do a graph. I have compensated for this so that the graph information is translated to the lower left corner and builds to the right.

Here is the point data I used in a file called: data.txt

``` 0,0
10,10
15,7.5
20,25
25,60
30,30
35,40
40,45
50,60```

I have included a couple of variations for computing this. Both are computing the graph such that the origin is the lower left corner of the canvas -- so it looks like a graph:

``` #**** PROGRAM 1 -- SIMPLE -- individual lines
# DECLARE THE SIZE OF THE CANVAS
set width 100
set height 100
canvas .c -width \$width -height \$height -background yellow
pack .c

set infile [open data.txt]
close \$infile

set count 0
foreach point \$indata {
if {\$count == 0} {
set oldx [lindex [split \$point ,] 0]
set oldy [expr \$height-[lindex [split \$point ,] 1]]
incr count
}
if {\$count > 0} {
set newx [lindex [split \$point ,] 0]
set newy [expr \$height-[lindex [split \$point ,] 1]]
.c create line \$oldx \$oldy \$newx \$newy
set oldx \$newx
set oldy \$newy
}
}```

PROGRAM 1 is a very simple program to follow, and it gets the job done, although not as efficient as other methods (see below). However, if you are a beginner - this will plot data and quickly. Believe me -- there are more efficient methods.

``` #**** PROGRAM 2-- MEDIUM/COMPLEX -- Single line for same data
# DECLARE THE SIZE OF THE CANVAS
set width 100
set height 100
canvas .c -width \$width -height \$height -background yellow
pack .c

set infile [open data.txt]
close \$infile

set linecoords {}
foreach point \$indata {
set x [lindex [split \$point ,] 0]
set y [expr \$height-[lindex [split \$point ,] 1]]
lappend linecoords \$x \$y
}
set test ".c create line [join \$linecoords]"
eval \$test```

PROGRAM 2 is more complicated because it uses more advanved functions of Tcl than the first program. The advantage is that your data set is described in one element. Which may be very helpful if you have multiple data sets to deal with.

RS: Here are some things I'd do differently, just for discussion:

• linecoords is a list, so it's slightly more efficient to initialize it as such
• splitting the \$point is done twice, once is enough
• accessing with lindex is OK, but for constant positions, I prefer foreach...break
• join is not needed, a list referenced in a string will come joined with spaces
``` set linecoords [list]
foreach point \$indata {
foreach {x y} [split \$point ,] break
lappend linecoords \$x [expr {\$height-\$y}]
}
eval .c create line \$linecoords```

SB 2002-10-27: I wanted to be able to plot comma-separated data with one x-value and one or more y-values where spaces basically are ignored so that I can use either csv data from excel or hand-crafted csv files manually modified for human readability. This work is based on the above examples.

The data file data.txt:

``` 0  ,0   ,1    ,56 ,0
10 ,10  ,5    ,70 ,10
15 ,7.5 ,7    ,65 ,15
20 ,25  ,9    ,10 ,20
25 ,60  ,19   ,4  ,25
30 ,30  ,12   ,3  ,30
35 ,40  ,56   ,16 ,35
40 ,45  ,12   ,45 ,40
50 ,60  ,34   ,22 ,50
60 ,45  ,17   ,23 ,60
70 ,10  ,15   ,50 ,70```

The program:

``` #**** PROGRAM 4-- COMPLEX
#

# DECLARE THE SIZE OF THE CANVAS
set width 100
set height 100
canvas .c -width \$width -height \$height -background white
pack .c

set infile [open "data.txt"]

# knowing that this way of reading a file may be bad
# I do it anyway and duck for cover
while {[gets \$infile indata] > -1} {
set i 0
set el [split \$indata ,]

# looking for something like car and cdr from lisp to process the el list
foreach {x yvals} [list [lindex \$el 0] [lrange \$el 1 end]] break

# process each y-value, the lists are not initialized anymore
# as I don't know how many datasets I have
foreach y \$yvals {
lappend linecoords(\$i) \$x [expr {\$height-\$y}]
incr i
}
}

close \$infile

# set some colors to be able to separate out each graph
array set color {
0       black
1       red
2       blue
3       magenta
}
for {set n 0} {\$n < \$i} {incr n} {
eval .c create line \$linecoords(\$n) -fill \$color(\$n)
}```

SB 2002-11-23: For some reason the eval statement in the for line above disappeared. Happened after the wiki started using css. Turned out to be one tab only in front. Replaced with spaces and the line shows again.

SB 2002-11-23: Now what happens if there are holes in a data range? In PROGRAM 4 this would lead to an error. The next example (PROGRAM 5) fix this. Additionally I have introduced strips: Instead of drawing all curves into one canvas, one canvas is created for each curve and then packed. This works well for a few strips, and an improvement will be a part of the next release.

First the data file data2.txt

``` 0  ,0   ,1    ,56 ,0
10 ,10  ,5    ,70 ,10
15 ,7.5 ,7    ,65 ,15
20 ,25  ,9    ,10 ,20
25 ,60  ,19   ,4  ,25
30 ,30  ,12   ,3  ,30
35 ,40  ,56   ,16 ,35
40 ,45  ,12   ,45 ,40
50 ,60  ,34   ,22 ,50
60 ,45  ,17   ,23 ,60
70 ,10  ,15   ,50 ,70
80 ,    ,     ,70 ,80
90 ,7.5 ,30   ,   ,
100,8   ,20   ,30 ,0```

The program:

``` #**** PROGRAM 5 -- COMPLEX -- y-data may be missing, strips.
#

# DECLARE THE SIZE OF THE CANVAS
set width 100
set height 100

set infile [open "data2.txt"]

while {[gets \$infile indata] > -1} {
set i 0
set el [split \$indata ,]

foreach {x yvals} [list [lindex \$el 0] [lrange \$el 1 end]] break

# Adding check for empty y-values the bash way
foreach y \$yvals {
if {[string trim "X\$y"] != "X"} {
lappend linecoords(\$i) \$x [expr {\$height-\$y}]
}
incr i
}
}

close \$infile

# set some colors to be able to separate out each graph
array set color {
0       black
1       red
2       blue
3       magenta
}

# Now each run through loop create and pack a separate canvas for each curve
for {set n 0} {\$n < \$i} {incr n} {
canvas .c\$n -width \$width -height \$height -background white
eval   .c\$n create line \$linecoords(\$n) -fill \$color(\$n)
pack   .c\$n
}```

SB 2002-11-24: For the next revision, PROGRAM 6, I reused some already existing code: ScrolledCanvas.tcl from canvas woes in order to save time and not reinvent the wheel. The only special thing to note is the way the frame containing the strip canvases is inserted into the ScrolledCanvas using the create window command available to canvases, and as the ScrolledCanvas is a composite widget we now have to use storage jars for the returned window paths, which is preferred for larger projects anyway.

When packing the strip canvases into the frame there is a padding on top which I can't explain, so I add an offset value of 4 to the calculated scrollheight for each strip to cover this.

The program:

``` #**** PROGRAM 6 -- COMPLEX -- y-data may be missing, strips, scrolling canvas.
#

source ScrolledCanvas.tcl

# DECLARE THE SIZE OF THE CANVAS
set width 100
set height 100

# Create the scrolled canvas and the window
set sc [ScrolledCanvas .c -width \$width -height \$height]
set sf [frame \$sc.f]

\$sc create window 0 0 -anchor nw -window \$sf
pack .c -expand true -fill both

set infile [open "data2.txt"]

while {[gets \$infile indata] > -1} {
set i 0
set el [split \$indata ,]

foreach {x yvals} [list [lindex \$el 0] [lrange \$el 1 end]] break

# Adding check for empty y-values the bash way
foreach y \$yvals {
if {[string trim "X\$y"] != "X"} {
lappend linecoords(\$i) \$x [expr {\$height-\$y}]
}
incr i
}
}

close \$infile

# set some colors to be able to separate out each graph
array set color {
0       black
1       red
2       blue
3       magenta
}

# Now each run through loop create and pack a separate canvas for each curve
for {set n 0} {\$n < \$i} {incr n} {
canvas \$sf.c\$n -width \$width -height \$height -background white
eval   \$sf.c\$n create line \$linecoords(\$n) -fill \$color(\$n)
pack   \$sf.c\$n
}

# Set the scroll height depending on number of strips
\$sc configure -scrollregion [list 0 0 \$width [expr {\$n * (\$height+4)}]]```