Keith Vetter 2010-04-20 -- Here's another extension to the tklib Plotchart package. It draws a graph similar to an xy-graph but by utilizing the 3d capabilities of Plotchart it turn the graph line into a 3d ribbon. This is a more robust version of my Ribbon Graphs package.
I'm using this code in a bike mapping program to draw route profile graphs.
I wrote one new routine, ::Plotchart::Draw3DRibbon, which takes as input a list of x,y duples (actually y,z due to 3d axis orientation). I slightly modified the 3d-axes routine, ::Plotchart::Draw3DAxes, to skip drawing any axis with an X compenent when xstep is 0.
One problem is that the graph isn't centered correctly, but my fix for that required tromping on too much of Plotchart's internals, so I left it out of this code.
AM (21 april 2010) Nice, I will add it to Plotchart
AM (18 june 2010) Incorporated it in my copy of the source code. To appear in Plotchart 1.9.0 (together with some other new plot types)

##+##########################################################################
#
# Plotchart extension--3d ribbon graph
# by Keith Vetter, April 2010
#
package require Plotchart
##+##########################################################################
#
# Draw3DRibbon -- new plotchart type which combines normal xy line
# graph but with a 3d ribbon affect.
#
# yzData consists of list of duples, each of which is y,z pair
# (y is left-to-right, z is up-and-down, x is front-to-back).
#
proc ::Plotchart::Draw3DRibbon { w yzData } {
variable scaling
set nxcells 1
set nycells [llength $yzData]
incr nxcells -1
incr nycells -1
set x1 $scaling($w,xmin)
set x2 [expr {($scaling($w,xmax) - $x1)/10.0}]
lassign $scaling($w,colours) fill border
#
# Draw the quadrangles making up the data in the right order:
# first y from minimum to maximum
# then x from maximum to minimum
#
for { set j 0 } { $j < $nycells } { incr j } {
set jj [expr {$j+1}]
set y1 [lindex $yzData $j 0]
set y2 [lindex $yzData $jj 0]
set z1 [lindex $yzData $j 1]
set z2 [lindex $yzData $jj 1]
lassign [::Plotchart::coords3DToPixel $w $x1 $y1 $z1] px11 py11
lassign [::Plotchart::coords3DToPixel $w $x1 $y2 $z2] px12 py12
lassign [::Plotchart::coords3DToPixel $w $x2 $y1 $z1] px21 py21
lassign [::Plotchart::coords3DToPixel $w $x2 $y2 $z2] px22 py22
$w create polygon $px11 $py11 $px21 $py21 $px22 $py22 \
$px12 $py12 $px11 $py11 \
-fill $fill -outline $border
}
}
##+##########################################################################
#
# A slight revision of Plotchart::Draw3DAxes differing only
# if xstep is 0 then we don't draw any axis that has an X
# component.
#
# Draw3DAxes --
# Draw the axes in a 3D plot
# Arguments:
# w Name of the canvas
# xmin Minimum x coordinate
# xmax Maximum x coordinate
# xstep Step size
# ymin Minimum y coordinate
# ymax Maximum y coordinate
# ystep Step size
# zmin Minimum z coordinate
# zmax Maximum z coordinate
# zstep Step size
# names List of labels for the x-axis (optional)
# Result:
# None
# Note:
# To keep the axes in positive orientation, the x-axis appears
# on the right-hand side and the y-axis appears in front.
# This may not be the most "intuitive" presentation though.
# Side effects:
# Axes drawn in canvas
#
proc ::Plotchart::Draw3DAxes { w
xmin ymin zmin
xmax ymax zmax
xstep ystep zstep
{names {}}} {
variable scaling
$w delete axis3d
#
# Create the support lines first
#
lassign [coords3DToPixel $w \
$scaling($w,xmin) $scaling($w,ymin) $scaling($w,zmin)] pxxmin pyxmin
lassign [coords3DToPixel $w \
$scaling($w,xmax) $scaling($w,ymin) $scaling($w,zmin)] pxxmax pyxmax
lassign [coords3DToPixel $w \
$scaling($w,xmax) $scaling($w,ymax) $scaling($w,zmin)] pxymax pyymax
lassign [coords3DToPixel $w \
$scaling($w,xmax) $scaling($w,ymin) $scaling($w,zmax)] pxzmax pyzmax
lassign [coords3DToPixel $w \
$scaling($w,xmin) $scaling($w,ymin) $scaling($w,zmax)] pxzmx2 pyzmx2
lassign [coords3DToPixel $w \
$scaling($w,xmin) $scaling($w,ymax) $scaling($w,zmin)] pxymx2 pyymx2
foreach [coords3DToPixel $w \
$scaling($w,xmax) $scaling($w,ymax) $scaling($w,zmax)] pxzymx pyzymx
if {$xstep > 0} {
$w create line $pxxmax $pyxmax $pxxmin $pyxmin -fill black -tag axis3d
$w create line $pxxmax $pyxmax $pxymax $pyymax -fill black -tag axis3d
$w create line $pxymax $pyymax $pxymx2 $pyymx2 -fill black -tag axis3d
$w create line $pxzmax $pyzmax $pxzymx $pyzymx -fill black -tag axis3d
$w create line $pxxmax $pyxmax $pxzmax $pyzmax -fill black -tag axis3d
$w create line $pxzmax $pyzmax $pxzmx2 $pyzmx2 -fill black -tag axis3d
$w create line $pxymax $pyymax $pxzymx $pyzymx -fill black -tag axis3d
}
$w create line $pxxmin $pyxmin $pxymx2 $pyymx2 -fill black -tag axis3d
$w create line $pxxmin $pyxmin $pxzmx2 $pyzmx2 -fill black -tag axis3d
#
# Numbers to the z-axis
#
set z $zmin
while { $z < $zmax+0.5*$zstep } {
lassign [coords3DToPixel $w $xmin $ymin $z] xcrd ycrd
set xcrd2 [expr {$xcrd-3}]
set xcrd3 [expr {$xcrd-5}]
$w create line $xcrd2 $ycrd $xcrd $ycrd -tag axis3d
$w create text $xcrd3 $ycrd -text $z -tag axis3d -anchor e
set z [expr {$z+$zstep}]
}
#
# Numbers or labels to the x-axis (shown on the right!)
#
if {$xstep > 0} {
if { $names eq "" } {
set x $xmin
while { $x < $xmax+0.5*$xstep } {
lassign [coords3DToPixel $w $x $ymax $zmin] xcrd ycrd
set xcrd2 [expr {$xcrd+4}]
set xcrd3 [expr {$xcrd+6}]
$w create line $xcrd2 $ycrd $xcrd $ycrd -tag axis3d
$w create text $xcrd3 $ycrd -text $x -tag axis3d -anchor w
set x [expr {$x+$xstep}]
}
} else {
set x [expr {$xmin+0.5*$xstep}]
foreach label $names {
lassign [coords3DToPixel $w $x $ymax $zmin] xcrd ycrd
set xcrd2 [expr {$xcrd+6}]
$w create text $xcrd2 $ycrd -text $label -tag axis3d -anchor w
set x [expr {$x+$xstep}]
}
}
}
#
# Numbers to the y-axis (shown in front!)
#
set y $ymin
while { $y < $ymax+0.5*$ystep } {
lassign [coords3DToPixel $w $xmin $y $zmin] xcrd ycrd
set ycrd2 [expr {$ycrd+3}]
set ycrd3 [expr {$ycrd+5}]
$w create line $xcrd $ycrd2 $xcrd $ycrd -tag axis3d
$w create text $xcrd $ycrd3 -text $y -tag axis3d -anchor n
set y [expr {$y+$ystep}]
}
set scaling($w,xstep) $xstep
set scaling($w,ystep) $ystep
set scaling($w,zstep) $zstep
#
# Set the default grid size
#
GridSize3D $w 10 10
}
################################################################
#
# Demo code
package require Tk
package require Plotchart
set yscale [list 0 40 5] ;# Y axis is miles along route
set zscale [list 900 1300 100] ;# Z axis is altitude
set xscale [list 0 .1 .1] ;# X axis is 3-d depth
# Each duple is distance along route (Y) and its altitude (Z)
set YZ {
{ 0 971} { 0.2 977} { 0.3 981} { 0.8 1010} { 1.0 1022} { 1.6 1060}
{ 1.7 1059} { 1.7 1060} { 1.8 1059} { 1.8 1059} { 1.8 1054} { 1.9 1071}
{ 2.0 1073} { 2.0 1060} { 2.0 1060} { 2.1 1050} { 2.2 1051} { 2.2 1049}
{ 2.3 1040} { 2.3 1039} { 2.4 1030} { 2.4 1050} { 2.5 1050} { 2.5 1059}
{ 2.6 1055} { 2.6 1052} { 2.6 1050} { 2.7 1041} { 2.7 1034} { 2.8 1025}
{ 2.9 1020} { 3.0 1049} { 3.2 1017} { 3.6 1010} { 3.9 1019} { 4.0 1029}
{ 4.1 1019} { 4.4 1049} { 4.6 1059} { 4.7 1067} { 4.8 1084} { 4.9 1029}
{ 5.2 1047} { 5.2 1058} { 5.7 1085} { 5.9 1087} { 6.4 1120} { 6.9 1169}
{ 7.3 1225} { 7.6 1265} { 8.0 1263} { 8.5 1189} { 8.8 1238} { 8.9 1245}
{ 9.1 1202} { 9.3 1199} {10.2 1148} {10.3 1148} {10.4 1141} {10.5 1135}
{10.7 1159} {11.1 1157} {11.3 1159} {11.5 1139} {11.6 1139} {11.8 1141}
{12.6 1160} {12.9 1150} {13.5 1150} {13.9 1150} {14.0 1129} {14.1 1120}
{14.4 1128} {14.5 1155} {14.7 1161} {14.7 1154} {14.8 1116} {14.9 1106}
{15.0 1110} {15.1 1113} {15.2 1104} {15.3 1108} {15.3 1118} {15.3 1131}
{15.3 1148} {15.4 1161} {15.5 1146} {15.6 1160} {15.9 1141} {16.1 1121}
{16.4 1100} {16.5 1102} {16.7 1097} {16.9 1088} {17.1 1091} {17.2 1082}
{17.2 1080} {17.4 1129} {17.5 1120} {17.6 1115} {17.7 1090} {18.0 1105}
{18.1 1109} {18.1 1104} {18.1 1084} {18.2 1071} {18.2 1091} {18.4 1073}
{18.5 1067} {18.5 1065} {18.6 1069} {18.8 1071} {19.2 1051} {19.2 1050}
{19.5 1051} {19.6 1059} {19.8 1046} {19.9 1048} {20.0 1053} {20.1 1039}
{20.3 1068} {20.7 1053} {20.8 1066} {21.0 1053} {21.1 1047} {21.3 1049}
{21.6 1048} {22.4 1019} {22.6 1026} {22.7 1030} {23.1 1026} {23.4 1054}
{24.1 1029} {24.9 1025} {25.1 1011} {25.2 1002} {25.3 1042} {25.3 1047}
{25.3 1048} {25.4 1049} {25.6 1080} {25.7 1090} {25.8 1089} {25.9 1107}
{26.0 1103} {27.0 1165} {27.0 1171} {27.1 1170} {27.2 1159} {27.2 1169}
{27.4 1180} {27.7 1154} {27.8 1139} {28.0 1139} {28.5 1130} {28.7 1139}
{29.1 1139} {29.3 1140} {29.4 1146} {29.5 1141} {29.5 1148} {29.6 1148}
{30.5 1199} {30.6 1202} {30.9 1245} {31.1 1238} {31.3 1189} {31.8 1263}
{32.2 1265} {32.6 1225} {32.9 1169} {33.4 1120} {33.9 1087} {34.2 1085}
{34.6 1058} {34.7 1047} {35.2 1012} {35.5 1001} {35.7 993} {35.8 989}
{35.9 1019} {36.2 1050} {36.2 1029} {36.3 1051} {36.4 1034} {36.8 1086}
{36.9 1067} {37.1 1121} {37.5 1053} {37.8 1059} {37.9 1060} {38.4 1022}
{38.6 1010} {39.1 981} {39.2 977} {39.5 971}
}
wm title . "3D Ribbon Plotchart"
canvas .c1 -width 800 -height 400 -bg #aaeeff -bd 0 -highlightthickness 0
pack .c1 -side top -fill both -expand 1
lassign $xscale xmin xmax xstep
lassign $yscale ymin ymax ystep
lassign $zscale zmin zmax zstep
# Example with full 3d axes
set s [::Plotchart::create3DPlot .c1 $xscale $yscale $zscale]
::Plotchart::Draw3DRibbon .c1 $YZ
# Example with minimal axes
canvas .c2 -width 800 -height 400 -bg #aaeeff -bd 0 -highlightthickness 0
pack .c2 -side top -fill both -expand 1 -pady {1 0}
set s [::Plotchart::create3DPlot .c2 $xscale $yscale $zscale]
# Redraw axis with xstep of 0 so it skips all X stuff
.c2 delete axis3d
::Plotchart::Draw3DAxes .c2 \
$xmin $ymin $zmin \
$xmin $ymax $zmax \
0 $ystep $zstep
::Plotchart::Draw3DRibbon .c2 $YZ
return