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