Radial Hierarchical Pareto Plot

I run into data graphing challenge: How to effectively draw a Pareto graph showing how a contributions are summed into a global sum in hierarchical manner.

The following is a widget that accepts a "tree" of the form: { topName totalSum { {child1 c1-sum { <c1-children list> } } { child2 c2-sum { .....} } ...}

########################################################################
#
# Radial Hierarchical Pareto
#

namespace eval redialHeirPareto {
    option add *RedialHeirPareto.size                        400
    option add *RedialHeirPareto.core                        20
    option add *RedialHeirPareto.maxLevels                   10

    option add *RedialHeirPareto.relief                      flat
    option add *RedialHeirPareto.borderWidth                 0

    option add *RedialHeirPareto.Canvas.width                200m
    option add *RedialHeirPareto.Canvas.height               200m
    option add *RedialHeirPareto.Canvas.background           gray
    option add *RedialHeirPareto.Canvas.foreground           black
    option add *RedialHeirPareto.Canvas.highlightThickness   0
    option add *RedialHeirPareto.Canvas.borderWidth          1
    option add *RedialHeirPareto.Canvas.relief               raised
}

proc redialHeirPareto::constructor { widget top hTreeName } {
        upvar      $hTreeName hTree
        
        frame $widget -class RedialHeirPareto
        canvas [set c $widget.canvas]
        pack $c -fill both -expand true
        
        option add ${widget}.hTreeName $hTreeName
        
        set size    [option get $widget size      RedialHeirPareto]
        set numLvls [option get $widget maxLevels RedialHeirPareto]
        set cH      [option get $widget core      RedialHeirPareto]

        set dH [expr ($size / 2.0 - $cH)/$numLvls]
        set mx $size
        set my $size

        # draw the hierarchies
        for {set h [expr $cH + $dH]} {$h <= $size / 2.0} {set h [expr $h + $dH]} {
                $c create oval \
                        [expr $mx - $h] [expr $my - $h] [expr $mx + $h] [expr $my + $h] \
                        -outline black 
        }
        
        # obtain the layout for each cell
        set objs [calcObjLayout $hTree]

        # now draw them
        foreach sl [lsort -index 1 -decreasing -integer $objs] {
                lassign $sl name lvl startAngle arcAngle val
                # first calc the box:
                set r [expr $cH + $lvl*$dH]
                set x0 [expr $mx - $r]
                set y0 [expr $my - $r]
                set x1 [expr $mx + $r]
                set y1 [expr $mx + $r]

                set color [getColor $arcAngle]
                set id [$c create arc $x0 $y0 $x1 $y1 \
                                          -start $startAngle -extent $arcAngle \
                                          -style pieslice -outline blue -fill $color]
                
                cballoon $c $id "$name $val"
        }

        # draw middle cicrle
        $c create oval \
                [expr $mx - $cH] [expr $my - $cH] [expr $mx + $cH] [expr $my + $cH] \
                -outline black -fill black
        
        $c create text $mx $my -anchor center -justify center -text $top -fill yellow        

        bind $c <2> [bind Text <2>]
        bind $c <B2-Motion> [bind Text <B2-Motion>]
        fit $c

        frame [set help $widget.help]
        label $help.l -text "Use: button-1 to show object properties and button-2 to drag"
        pack $help.l -fill both -expand true 
        pack $help -fill both -expand true -side bottom
        
        return $widget
}

# This needs more work as it has bugs if run within the <Configure>
# event. Does not maximize to full window.
proc redialHeirPareto::fit {c} {
        set bbox [$c bbox all]
        lassign $bbox x0 y0 x1 y1
        set mx [expr ($x1 + $x0)/2]
        set my [expr ($y1 + $y0)/2]
        set dx [expr $x1 - $x0]
        set dy [expr $y1 - $y0]
        set w  [$c cget -width]
        set h  [$c cget -height]
        $c xview moveto 0
        $c yview moveto 0
#        $c configure -scrollregion [list 0 0 $w $h]
#        set vw [lindex [$c xview] 1]
#        set vh [lindex [$c yview] 1]
#        if {$vw != 0 && $vh != 0} {
#                set w [expr $w*$vw]
#                set h [expr $h*$vh]
#   }
        $c scale all [expr 1.0*$mx*$w/$dx] [expr 1.0*$my*$h / $dy] [expr 1.0*$w / $dx] [expr 1.0*$h / $dy]
}

proc redialHeirPareto::getColor {arcAngle} {
        set color [format "#%02x%02x%02x" 255 [expr 255-int(255*($arcAngle/360))] 0]
}

# Calculate the allocated start stop angle for each object
# The result is a list of {key lvl startAngle stopAngle}
proc redialHeirPareto::calcObjLayout {hTree {startAngle 0} {stopAngle 359} {lvl 1} } {
        set res {} 
        lassign $hTree top sum children

        if {![llength $children]} {return ""}

        set anglePerUnit [expr 1.0*($stopAngle - $startAngle) / $sum]
        set angle $startAngle

        foreach child [lsort -decreasing -index 1 -real $children] {
                lassign $child name val
                set endAngle [expr $angle + $anglePerUnit * $val]
                set a1 [expr $anglePerUnit * $val]
                lappend res [list $name $lvl $angle $a1 $val]
                append res " " [calcObjLayout $child $angle $endAngle [expr $lvl + 1]]
                set angle $endAngle
        }
        return $res
}

proc redialHeirPareto::cballoon {w tag text} {
        $w bind $tag <ButtonPress-1> [list redialHeirPareto::cballoon_disp $w $text]
        $w bind all  <ButtonRelease-1> [list after 1 $w delete cballoon]
}

proc redialHeirPareto::cballoon_disp {w text} {
        lassign [$w bbox current] - - x y
        if [info exists y] {
                set id [$w create text $x $y -text $text -tag cballoon]
                lassign [$w bbox $id] x0 y0 x1 y1
                $w create rect $x0 $y0 $x1 $y1 -fill lightyellow -tag cballoon
                $w raise $id
        }
}

AM (11 february 2008) Could you add an example that is not too trivial? It seems interesting enough :). (By the way: you have consistently used "redial" instead of "radial")

aspect: updated formatting. Not entirely sure how to use this -- if someone could add an example that would be great!

EG: you should Brace your expr-essions