[3330] | 1 | # -*- mode: tcl; indent-tabs-mode: nil -*- |
---|
[1550] | 2 | |
---|
[811] | 3 | # ---------------------------------------------------------------------- |
---|
| 4 | # COMPONENT: histogram - extracts data from an XML description of a field |
---|
| 5 | # |
---|
| 6 | # This object represents a histogram of data in an XML description of |
---|
| 7 | # simulator output. A histogram is similar to a field, but a field is |
---|
| 8 | # a quantity versus position in device. A histogram is any quantity |
---|
| 9 | # versus any other quantity. This class simplifies the process of |
---|
| 10 | # extracting data vectors that represent the histogram. |
---|
| 11 | # ====================================================================== |
---|
| 12 | # AUTHOR: Michael McLennan, Purdue University |
---|
[3177] | 13 | # Copyright (c) 2004-2012 HUBzero Foundation, LLC |
---|
[811] | 14 | # |
---|
| 15 | # See the file "license.terms" for information on usage and |
---|
| 16 | # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
---|
| 17 | # ====================================================================== |
---|
| 18 | package require Itcl |
---|
| 19 | package require BLT |
---|
| 20 | |
---|
| 21 | namespace eval Rappture { # forward declaration } |
---|
| 22 | |
---|
| 23 | itcl::class Rappture::Histogram { |
---|
| 24 | constructor {xmlobj path} { # defined below } |
---|
| 25 | destructor { # defined below } |
---|
| 26 | |
---|
| 27 | public method components {{pattern *}} |
---|
[2388] | 28 | public method mesh { component } |
---|
| 29 | public method values { component } |
---|
| 30 | public method widths { component } |
---|
| 31 | public method xlabels { component } |
---|
[811] | 32 | public method limits {which} |
---|
| 33 | public method xmarkers {} |
---|
| 34 | public method ymarkers {} |
---|
| 35 | public method hints {{key ""}} |
---|
| 36 | |
---|
[2388] | 37 | protected method Build {} |
---|
| 38 | private method Clear { {comp ""} } |
---|
| 39 | private method ParseData { comp } |
---|
[811] | 40 | |
---|
| 41 | private variable _xmlobj "" ;# ref to lib obj with histogram data |
---|
| 42 | private variable _hist "" ;# lib obj representing this histogram |
---|
[2388] | 43 | private variable _widths ;# array of vectors of bin widths |
---|
| 44 | private variable _yvalues ;# array of vectors of bin heights along |
---|
[2744] | 45 | ;# y-axis. |
---|
[2388] | 46 | private variable _xvalues ;# array of vectors of bin locations along |
---|
[2744] | 47 | ;# x-axis. |
---|
[2388] | 48 | private variable _xlabels ;# array of labels |
---|
[811] | 49 | private variable _hints ;# cache of hints stored in XML |
---|
| 50 | private variable _xmarkers "";# list of {x,label,options} triplets. |
---|
| 51 | private variable _ymarkers "";# list of {y,label,options} triplets. |
---|
| 52 | private common _counter 0 ;# counter for unique vector names |
---|
[2388] | 53 | private variable _comp2hist ;# maps component name => x,y,w,l vectors |
---|
[811] | 54 | } |
---|
| 55 | |
---|
| 56 | # ---------------------------------------------------------------------- |
---|
| 57 | # CONSTRUCTOR |
---|
| 58 | # ---------------------------------------------------------------------- |
---|
| 59 | itcl::body Rappture::Histogram::constructor {xmlobj path} { |
---|
| 60 | if {![Rappture::library isvalid $xmlobj]} { |
---|
[1929] | 61 | error "bad value \"$xmlobj\": should be LibraryObj" |
---|
[811] | 62 | } |
---|
| 63 | set _xmlobj $xmlobj |
---|
| 64 | set _hist [$xmlobj element -as object $path] |
---|
| 65 | |
---|
| 66 | # build up vectors for various components of the histogram |
---|
[2388] | 67 | Build |
---|
[811] | 68 | } |
---|
| 69 | |
---|
| 70 | # ---------------------------------------------------------------------- |
---|
| 71 | # DESTRUCTOR |
---|
| 72 | # ---------------------------------------------------------------------- |
---|
| 73 | itcl::body Rappture::Histogram::destructor {} { |
---|
[2388] | 74 | # don't destroy the _xmlobj! we don't own it! |
---|
[811] | 75 | itcl::delete object $_hist |
---|
[2388] | 76 | Clear |
---|
[811] | 77 | } |
---|
| 78 | |
---|
| 79 | # ---------------------------------------------------------------------- |
---|
[2388] | 80 | # USAGE: mesh |
---|
[811] | 81 | # |
---|
| 82 | # Returns the vector for the histogram bin locations along the |
---|
| 83 | # x-axis. |
---|
| 84 | # ---------------------------------------------------------------------- |
---|
[2388] | 85 | itcl::body Rappture::Histogram::mesh { comp } { |
---|
| 86 | if { [info exists _xvalues($comp)] } { |
---|
[2744] | 87 | return $_xvalues($comp) |
---|
[2388] | 88 | } |
---|
| 89 | return "" |
---|
[811] | 90 | } |
---|
| 91 | |
---|
| 92 | # ---------------------------------------------------------------------- |
---|
| 93 | # USAGE: heights |
---|
| 94 | # |
---|
| 95 | # Returns the vector for the histogram bin heights along the y-axis. |
---|
| 96 | # ---------------------------------------------------------------------- |
---|
[2388] | 97 | itcl::body Rappture::Histogram::values { comp } { |
---|
| 98 | if { [info exists _yvalues($comp)] } { |
---|
[2744] | 99 | return $_yvalues($comp) |
---|
[2388] | 100 | } |
---|
| 101 | return "" |
---|
[811] | 102 | } |
---|
| 103 | |
---|
| 104 | # ---------------------------------------------------------------------- |
---|
| 105 | # USAGE: widths |
---|
| 106 | # |
---|
| 107 | # Returns the vector for the specified histogram component <name>. |
---|
| 108 | # If the name is not specified, then it returns the vectors for the |
---|
| 109 | # overall histogram (sum of all components). |
---|
| 110 | # ---------------------------------------------------------------------- |
---|
[2388] | 111 | itcl::body Rappture::Histogram::widths { comp } { |
---|
| 112 | if { [info exists _widths($comp)] } { |
---|
[2744] | 113 | return $_widths($comp) |
---|
[2388] | 114 | } |
---|
| 115 | return "" |
---|
[811] | 116 | } |
---|
| 117 | |
---|
| 118 | # ---------------------------------------------------------------------- |
---|
[2388] | 119 | # USAGE: xlabels |
---|
| 120 | # |
---|
| 121 | # Returns the vector for the specified histogram component <name>. |
---|
| 122 | # If the name is not specified, then it returns the vectors for the |
---|
| 123 | # overall histogram (sum of all components). |
---|
| 124 | # ---------------------------------------------------------------------- |
---|
| 125 | itcl::body Rappture::Histogram::xlabels { comp } { |
---|
| 126 | if { [info exists _xlabels($comp)] } { |
---|
[2744] | 127 | return $_xlabels($comp) |
---|
[2388] | 128 | } |
---|
| 129 | return "" |
---|
| 130 | } |
---|
| 131 | |
---|
| 132 | # ---------------------------------------------------------------------- |
---|
[811] | 133 | # USAGE: xmarkers |
---|
| 134 | # |
---|
| 135 | # Returns the list of settings for each marker on the x-axis. |
---|
| 136 | # If no markers have been specified the empty string is returned. |
---|
| 137 | # ---------------------------------------------------------------------- |
---|
| 138 | itcl::body Rappture::Histogram::xmarkers {} { |
---|
| 139 | return $_xmarkers; |
---|
| 140 | } |
---|
| 141 | |
---|
| 142 | # ---------------------------------------------------------------------- |
---|
[2388] | 143 | # USAGE: components ?<pattern>? |
---|
| 144 | # |
---|
| 145 | # Returns a list of names for the various components of this curve. |
---|
| 146 | # If the optional glob-style <pattern> is specified, then it returns |
---|
| 147 | # only the component names matching the pattern. |
---|
| 148 | # ---------------------------------------------------------------------- |
---|
| 149 | itcl::body Rappture::Histogram::components {{pattern *}} { |
---|
| 150 | set rlist "" |
---|
| 151 | foreach name [array names _comp2hist] { |
---|
| 152 | if {[string match $pattern $name]} { |
---|
| 153 | lappend rlist $name |
---|
| 154 | } |
---|
| 155 | } |
---|
| 156 | return $rlist |
---|
| 157 | } |
---|
| 158 | |
---|
| 159 | # ---------------------------------------------------------------------- |
---|
[811] | 160 | # USAGE: ymarkers |
---|
| 161 | # |
---|
| 162 | # Returns the list of settings for each marker on the y-axis. |
---|
| 163 | # If no markers have been specified the empty string is returned. |
---|
| 164 | # ---------------------------------------------------------------------- |
---|
| 165 | itcl::body Rappture::Histogram::ymarkers {} { |
---|
| 166 | return $_ymarkers; |
---|
| 167 | } |
---|
| 168 | |
---|
| 169 | # ---------------------------------------------------------------------- |
---|
| 170 | # USAGE: limits x|xlin|xlog|y|ylin|ylog |
---|
| 171 | # |
---|
| 172 | # Returns the {min max} limits for the specified axis. |
---|
| 173 | # |
---|
| 174 | # What does it mean to view a distribution (the bins) as log scale? |
---|
| 175 | # |
---|
| 176 | # ---------------------------------------------------------------------- |
---|
| 177 | itcl::body Rappture::Histogram::limits {which} { |
---|
| 178 | set min "" |
---|
| 179 | set max "" |
---|
| 180 | switch -- $which { |
---|
[2388] | 181 | x - xlin { set pos 0; set log 0; set axis xaxis } |
---|
| 182 | xlog { set pos 0; set log 1; set axis xaxis } |
---|
| 183 | y - ylin - v - vlin { set pos 1; set log 0; set axis yaxis } |
---|
| 184 | ylog - vlog { set pos 1; set log 1; set axis yaxis } |
---|
| 185 | default { |
---|
| 186 | error "bad option \"$which\": should be x, xlin, xlog, y, ylin, ylog, v, vlin, vlog" |
---|
[1929] | 187 | } |
---|
[2388] | 188 | } |
---|
| 189 | |
---|
| 190 | blt::vector create tmp |
---|
| 191 | blt::vector create zero |
---|
| 192 | foreach comp [array names _comphist] { |
---|
| 193 | set vname [lindex $_comp2hist($comp) $pos] |
---|
| 194 | $vname variable vec |
---|
| 195 | |
---|
| 196 | if {$log} { |
---|
| 197 | # on a log scale, use abs value and ignore 0's |
---|
| 198 | $vname dup tmp |
---|
| 199 | $vname dup zero |
---|
| 200 | zero expr {tmp == 0} ;# find the 0's |
---|
| 201 | tmp expr {abs(tmp)} ;# get the abs value |
---|
| 202 | tmp expr {tmp + zero*max(tmp)} ;# replace 0's with abs max |
---|
| 203 | set vmin [blt::vector expr min(tmp)] |
---|
| 204 | set vmax [blt::vector expr max(tmp)] |
---|
| 205 | } else { |
---|
| 206 | set vmin $vec(min) |
---|
| 207 | set vmax $vec(max) |
---|
[1929] | 208 | } |
---|
[2388] | 209 | |
---|
| 210 | if {"" == $min} { |
---|
| 211 | set min $vmin |
---|
| 212 | } elseif {$vmin < $min} { |
---|
| 213 | set min $vmin |
---|
[1929] | 214 | } |
---|
[2388] | 215 | if {"" == $max} { |
---|
| 216 | set max $vmax |
---|
| 217 | } elseif {$vmax > $max} { |
---|
| 218 | set max $vmax |
---|
[1929] | 219 | } |
---|
[811] | 220 | } |
---|
| 221 | blt::vector destroy tmp zero |
---|
| 222 | |
---|
| 223 | set val [$_hist get $axis.min] |
---|
| 224 | if {"" != $val && "" != $min} { |
---|
[1929] | 225 | if {$val > $min} { |
---|
| 226 | # tool specified this min -- don't go any lower |
---|
| 227 | set min $val |
---|
| 228 | } |
---|
[811] | 229 | } |
---|
| 230 | |
---|
| 231 | set val [$_hist get $axis.max] |
---|
| 232 | if {"" != $val && "" != $max} { |
---|
[1929] | 233 | if {$val < $max} { |
---|
| 234 | # tool specified this max -- don't go any higher |
---|
| 235 | set max $val |
---|
| 236 | } |
---|
[811] | 237 | } |
---|
| 238 | return [list $min $max] |
---|
| 239 | } |
---|
| 240 | |
---|
| 241 | # ---------------------------------------------------------------------- |
---|
| 242 | # USAGE: hints ?<keyword>? |
---|
| 243 | # |
---|
| 244 | # Returns a list of key/value pairs for various hints about plotting |
---|
| 245 | # this histogram. If a particular <keyword> is specified, then it returns |
---|
| 246 | # the hint for that <keyword>, if it exists. |
---|
| 247 | # ---------------------------------------------------------------------- |
---|
| 248 | itcl::body Rappture::Histogram::hints {{keyword ""}} { |
---|
| 249 | if {![info exists _hints]} { |
---|
[1929] | 250 | foreach {key path} { |
---|
| 251 | group about.group |
---|
| 252 | label about.label |
---|
| 253 | color about.color |
---|
| 254 | style about.style |
---|
| 255 | type about.type |
---|
| 256 | xlabel xaxis.label |
---|
| 257 | xdesc xaxis.description |
---|
| 258 | xunits xaxis.units |
---|
[2778] | 259 | xorient xaxis.orientation |
---|
[1929] | 260 | xscale xaxis.scale |
---|
| 261 | xmin xaxis.min |
---|
| 262 | xmax xaxis.max |
---|
| 263 | ylabel yaxis.label |
---|
| 264 | ydesc yaxis.description |
---|
| 265 | yunits yaxis.units |
---|
| 266 | yscale yaxis.scale |
---|
| 267 | ymin yaxis.min |
---|
| 268 | ymax yaxis.max |
---|
| 269 | } { |
---|
| 270 | set str [$_hist get $path] |
---|
| 271 | if {"" != $str} { |
---|
| 272 | set _hints($key) $str |
---|
| 273 | } |
---|
| 274 | } |
---|
[811] | 275 | |
---|
[1929] | 276 | if {[info exists _hints(xlabel)] && "" != $_hints(xlabel) |
---|
| 277 | && [info exists _hints(xunits)] && "" != $_hints(xunits)} { |
---|
| 278 | set _hints(xlabel) "$_hints(xlabel) ($_hints(xunits))" |
---|
| 279 | } |
---|
| 280 | if {[info exists _hints(ylabel)] && "" != $_hints(ylabel) |
---|
| 281 | && [info exists _hints(yunits)] && "" != $_hints(yunits)} { |
---|
| 282 | set _hints(ylabel) "$_hints(ylabel) ($_hints(yunits))" |
---|
| 283 | } |
---|
[811] | 284 | |
---|
[1929] | 285 | if {[info exists _hints(group)] && [info exists _hints(label)]} { |
---|
| 286 | # pop-up help for each histogram |
---|
| 287 | set _hints(tooltip) $_hints(label) |
---|
| 288 | } |
---|
[811] | 289 | } |
---|
| 290 | |
---|
| 291 | if {$keyword != ""} { |
---|
[1929] | 292 | if {[info exists _hints($keyword)]} { |
---|
| 293 | return $_hints($keyword) |
---|
| 294 | } |
---|
| 295 | return "" |
---|
[811] | 296 | } |
---|
| 297 | return [array get _hints] |
---|
| 298 | } |
---|
| 299 | |
---|
| 300 | # ---------------------------------------------------------------------- |
---|
[2388] | 301 | # USAGE: Build |
---|
[811] | 302 | # |
---|
| 303 | # Used internally to build up the vector representation for the |
---|
| 304 | # histogram when the object is first constructed, or whenever the histogram |
---|
| 305 | # data changes. Discards any existing vectors and builds everything |
---|
| 306 | # from scratch. |
---|
| 307 | # ---------------------------------------------------------------------- |
---|
[2388] | 308 | itcl::body Rappture::Histogram::Build {} { |
---|
[811] | 309 | # discard any existing data |
---|
[2388] | 310 | Clear |
---|
[811] | 311 | # |
---|
| 312 | # Scan through the components of the histogram and create |
---|
| 313 | # vectors for each part. Right now there's only one |
---|
| 314 | # component. I left in the component tag in case future |
---|
| 315 | # enhancements require more than one component. |
---|
| 316 | # |
---|
[2388] | 317 | foreach cname [$_hist children -type component] { |
---|
[2744] | 318 | ParseData $cname |
---|
[811] | 319 | } |
---|
| 320 | # Creates lists of x and y marker data. |
---|
| 321 | set _xmarkers {} |
---|
| 322 | set _ymarkers {} |
---|
| 323 | foreach cname [$_hist children -type "marker" xaxis] { |
---|
[1929] | 324 | set at [$_hist get "xaxis.$cname.at"] |
---|
| 325 | set label [$_hist get "xaxis.$cname.label"] |
---|
| 326 | set styles [$_hist get "xaxis.$cname.style"] |
---|
| 327 | set data [list $at $label $styles] |
---|
| 328 | lappend _xmarkers $data |
---|
[811] | 329 | } |
---|
| 330 | foreach cname [$_hist children -type "marker" yaxis] { |
---|
[1929] | 331 | set at [$_hist get "yaxis.$cname.at"] |
---|
| 332 | set label [$_hist get "yaxis.$cname.label"] |
---|
| 333 | set styles [$_hist get "yaxis.$cname.style"] |
---|
| 334 | set data [list $at $label $styles] |
---|
| 335 | lappend _xmarkers $data |
---|
[811] | 336 | } |
---|
| 337 | } |
---|
[2388] | 338 | |
---|
| 339 | # |
---|
| 340 | # ParseData -- |
---|
| 341 | # |
---|
[2744] | 342 | # Parse the components data representations. The following |
---|
| 343 | # elements may be used <xy>, <xhw>, <namevalue>, <xvector>, |
---|
| 344 | # <yvector>. Only one element is used for data. |
---|
[2388] | 345 | # |
---|
| 346 | itcl::body Rappture::Histogram::ParseData { comp } { |
---|
| 347 | # Create new vectors or discard any existing data |
---|
| 348 | set _xvalues($comp) [blt::vector create \#auto] |
---|
| 349 | set _yvalues($comp) [blt::vector create \#auto] |
---|
| 350 | set _widths($comp) [blt::vector create \#auto] |
---|
| 351 | set _xlabels($comp) {} |
---|
| 352 | |
---|
| 353 | set xydata [$_hist get ${comp}.xy] |
---|
| 354 | if { $xydata != "" } { |
---|
[2744] | 355 | set count 0 |
---|
[2388] | 356 | foreach line [split $xydata \n] { |
---|
[3010] | 357 | if {[llength $line] == 2} { |
---|
| 358 | foreach {name value} $line break |
---|
| 359 | $_yvalues($comp) append $value |
---|
| 360 | $_xvalues($comp) append $count |
---|
| 361 | lappend _xlabels($comp) $name |
---|
| 362 | incr count |
---|
| 363 | } |
---|
[2744] | 364 | } |
---|
| 365 | set _comp2hist($comp) [list $_xvalues($comp) $_yvalues($comp)] |
---|
| 366 | return |
---|
[2388] | 367 | } |
---|
| 368 | set xhwdata [$_hist get ${comp}.xhw] |
---|
| 369 | if { $xhwdata != "" } { |
---|
[2744] | 370 | set count 0 |
---|
[2388] | 371 | foreach line [split $xhwdata \n] { |
---|
| 372 | set n [scan $line {%s %s %s} name h w] |
---|
[3010] | 373 | if {$n >= 2} { |
---|
| 374 | lappend _xlabels($comp) $name |
---|
| 375 | $_xvalues($comp) append $count |
---|
| 376 | $_yvalues($comp) append $h |
---|
| 377 | if { $n == 3 } { |
---|
| 378 | $_widths($comp) append $w |
---|
| 379 | } |
---|
| 380 | incr count |
---|
[2388] | 381 | } |
---|
[2744] | 382 | } |
---|
| 383 | set _comp2hist($comp) [list $_xvalues($comp) $_yvalues($comp)] |
---|
| 384 | return |
---|
[2388] | 385 | |
---|
| 386 | # FIXME: There must be a width specified for each bin location. |
---|
[2744] | 387 | # If this isn't true, we default to uniform widths |
---|
| 388 | # (zero-length _widths vector == uniform). |
---|
[2388] | 389 | if { [$_xvalues($comp) length] != [$_widths($comp) length] } { |
---|
| 390 | $_widths($comp) set {} |
---|
| 391 | } |
---|
[2744] | 392 | set _comp2hist($comp) [list $_xvalues($comp) $_yvalues($comp)] |
---|
| 393 | return |
---|
[2388] | 394 | } |
---|
| 395 | set xv [$_hist get $comp.xvector] |
---|
| 396 | set yv [$_hist get $comp.yvector] |
---|
| 397 | if { $xv != "" && $yv != "" } { |
---|
[2744] | 398 | $_yvalues($comp) set $yv |
---|
| 399 | $_xvalues($comp) seq 0 [$yv length] |
---|
| 400 | set _xlabels($comp) |
---|
[2388] | 401 | } |
---|
| 402 | set _comp2hist($comp) [list $_xvalues($comp) $_yvalues($comp)] |
---|
| 403 | } |
---|
| 404 | |
---|
| 405 | itcl::body Rappture::Histogram::Clear { {comp ""} } { |
---|
| 406 | if { $comp == "" } { |
---|
[2744] | 407 | foreach name [array names _widths] { |
---|
| 408 | blt::vector destroy $_widths($name) |
---|
| 409 | } |
---|
| 410 | array unset _widths |
---|
| 411 | foreach name [array names _yvalues] { |
---|
| 412 | blt::vector destroy $_yvalues($name) |
---|
| 413 | } |
---|
| 414 | array unset _yvalues |
---|
| 415 | foreach name [array names _xvalues] { |
---|
| 416 | blt::vector destroy $_xvalues($name) |
---|
| 417 | } |
---|
| 418 | array unset _xvalues |
---|
| 419 | array unset _xlabels |
---|
| 420 | array unset _comp2hist |
---|
| 421 | return |
---|
[2388] | 422 | } |
---|
| 423 | if { [info exists _widths($comp)] } { |
---|
[2744] | 424 | blt::vector destroy $_widths($comp) |
---|
[2388] | 425 | } |
---|
| 426 | if { [info exists _yvalues($comp)] } { |
---|
[2744] | 427 | blt::vector destroy $_yvalues($comp) |
---|
[2388] | 428 | } |
---|
| 429 | if { [info exists _xvalues($comp)] } { |
---|
[2744] | 430 | blt::vector destroy $_xvalues($comp) |
---|
[2388] | 431 | } |
---|
| 432 | array unset _xvalues $comp |
---|
| 433 | array unset _yvalues $comp |
---|
| 434 | array unset _widths $comp |
---|
| 435 | array unset _xlabels $comp |
---|
| 436 | array unset _comp2hist $comp |
---|
| 437 | } |
---|
| 438 | |
---|