Extensions to Plotchart 2

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)


https://wiki.tcl-lang.org/_repo/wiki_images/ribbon_screenshot.png


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

    foreach {fill border} $scaling($w,colours) {break}

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

        foreach {px11 py11} [::Plotchart::coords3DToPixel $w $x1 $y1 $z1] break
        foreach {px12 py12} [::Plotchart::coords3DToPixel $w $x1 $y2 $z2] break
        foreach {px21 py21} [::Plotchart::coords3DToPixel $w $x2 $y1 $z1] break
        foreach {px22 py22} [::Plotchart::coords3DToPixel $w $x2 $y2 $z2] break
        $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
    #
    foreach {pxxmin pyxmin} [coords3DToPixel $w \
           $scaling($w,xmin) $scaling($w,ymin) $scaling($w,zmin)] {break}
    foreach {pxxmax pyxmax} [coords3DToPixel $w \
           $scaling($w,xmax) $scaling($w,ymin) $scaling($w,zmin)] {break}
    foreach {pxymax pyymax} [coords3DToPixel $w \
           $scaling($w,xmax) $scaling($w,ymax) $scaling($w,zmin)] {break}
    foreach {pxzmax pyzmax} [coords3DToPixel $w \
           $scaling($w,xmax) $scaling($w,ymin) $scaling($w,zmax)] {break}
    foreach {pxzmx2 pyzmx2} [coords3DToPixel $w \
           $scaling($w,xmin) $scaling($w,ymin) $scaling($w,zmax)] {break}
    foreach {pxymx2 pyymx2} [coords3DToPixel $w \
           $scaling($w,xmin) $scaling($w,ymax) $scaling($w,zmin)] {break}
    foreach {pxzymx pyzymx} [coords3DToPixel $w \
           $scaling($w,xmax) $scaling($w,ymax) $scaling($w,zmax)] {break}

    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 } {
        foreach {xcrd ycrd} [coords3DToPixel $w $xmin $ymin $z] {break}
        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 } {
                foreach {xcrd ycrd} [coords3DToPixel $w $x $ymax $zmin] {break}
                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 {
                foreach {xcrd ycrd} [coords3DToPixel $w $x $ymax $zmin] {break}
                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 } {
        foreach {xcrd ycrd} [coords3DToPixel $w $xmin $y $zmin] {break}
        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

foreach {xmin xmax xstep} $xscale break
foreach {ymin ymax ystep} $yscale break
foreach {zmin zmax zstep} $zscale break

# 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