Version 2 of Extensions to Plotchart 2

Updated 2010-04-21 09:48:28 by arjen

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


http://wiki.tcl.tk/_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