source: branches/blt4/gui/scripts/xylegend.tcl @ 1897

Last change on this file since 1897 was 1804, checked in by gah, 14 years ago
File size: 19.3 KB
Line 
1
2# ----------------------------------------------------------------------
3#  COMPONENT: xylegend - X/Y plot legend.
4#
5#  This widget is a legend for an X/Y plot, meant to view line graphs produced
6#  as output from the run of a Rappture tool.  Use the "add" and
7#  "delete" methods to control the curves showing on the plot.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2005  Purdue Research Foundation
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15package require Itk
16package require BLT
17
18option add *Xylegend.font \
19    -*-helvetica-medium-r-normal-*-8-* widgetDefault
20
21option add *Xylegend.Button.font \
22    -*-helvetica-medium-r-normal-*-9-* widgetDefault
23
24itcl::class ::Rappture::XyLegend {
25    inherit itk::Widget
26
27    private variable _autocolors {
28        #0000cd
29        #cd0000
30        #00cd00
31        #3a5fcd
32        #cdcd00
33        #cd1076
34        #009acd
35        #00c5cd
36        #a2b5cd
37        #7ac5cd
38        #66cdaa
39        #a2cd5a
40        #cd9b9b
41        #cdba96
42        #cd3333
43        #cd6600
44        #cd8c95
45        #cd00cd
46        #9a32cd
47        #6ca6cd
48        #9ac0cd
49        #9bcd9b
50        #00cd66
51        #cdc673
52        #cdad00
53        #cd5555
54        #cd853f
55        #cd7054
56        #cd5b45
57        #cd6889
58        #cd69c9
59        #551a8b
60    }
61    private variable _lastColorIndex ""
62    private variable _dispatcher "" ;# dispatcher for !events
63    private variable _graph     ""
64    private variable _tree      ""
65    private variable _diff      "";     # Polygon marker used for difference.
66    private variable _rename    "";     # Node selected to be renamed.
67    private variable _diffelements
68    private variable _unmapHidden 0
69
70    constructor {args} { graph }
71    destructor {}
72
73    public method reset {}
74
75    private method Add { elem label {flags ""}}
76    private method Average {}
77    private method BuildPopup { popup }
78    private method Check { menu }
79    private method Delete { args }
80    private method Difference {}
81    private method Editor { option args }
82    private method GetData { elem what }
83    private method Hide { args }
84    private method Lower { args }
85    private method Raise { args }
86    private method PopupMenu { x y }
87    private method Recolor {}
88    private method Rename {}
89    private method SelectAll {}
90    private method Show { args }
91    private method Toggle { args }
92    private method UnmapHidden {}
93}
94                                                                               
95itk::usual XyLegend {
96    keep -background -foreground -cursor
97}
98
99itk::usual ComboEntry {
100}
101itk::usual ComboMenu {
102}
103itk::usual TreeView {
104    keep -foreground -cursor
105}
106itk::usual Scrollset {
107}
108itk::usual ComboMenu {
109}
110itk::usual ComboEntry {
111}
112
113blt::bitmap define dot1 {
114#define dot1_width 8
115#define dot1_height 8
116static unsigned char dot1_bits[] = {
117   0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa};
118}
119
120# ----------------------------------------------------------------------
121# CONSTRUCTOR
122# ----------------------------------------------------------------------
123itcl::body Rappture::XyLegend::constructor { graph args } {
124    option add hull.width hull.height
125    pack propagate $itk_component(hull) no
126    itk_component add scrollbars {
127        blt::scrollset $itk_interior.scrl \
128            -yscrollbar $itk_interior.scrl.ys \
129            -window $itk_interior.scrl.legend \
130            -height 100
131    }
132    blt::tk::scrollbar $itk_interior.scrl.ys
133    set _tree [blt::tree create]
134    itk_component add legend {
135        blt::treeview $itk_component(scrollbars).legend -linewidth 0 \
136            -bg white -selectmode multiple \
137            -highlightthickness 0 \
138            -tree $_tree \
139            -font "Arial 9" \
140            -flat yes -separator /
141    }
142    $itk_component(legend) column insert 0 "show" \
143        -text "" -weight 0.0 -pad 0 -borderwidth 0
144    $itk_component(legend) style checkbox "check" -showvalue no \
145        -onvalue 0 -offvalue 1
146    $itk_component(legend) column configure "treeView" -justify left \
147        -weight 1.0 -text "" -pad 0 -borderwidth 0 -edit no
148    $itk_component(legend) column configure "show" -style "check" -pad {0 0} \
149        -edit yes
150    set m $itk_component(hull).mb.menu
151    blt::combobutton $itk_component(hull).mb  \
152        -icon [Rappture::icon wrench] \
153        -menu $m -activerelief raised -relief flat
154
155    itk_component add controls {
156        blt::combomenu $m \
157            -yscrollbar $m.ys \
158            -xscrollbar $m.xs
159    }
160    blt::tk::scrollbar $m.xs
161    blt::tk::scrollbar $m.ys
162
163    $m add -text "Hide" -command [itcl::code $this Hide]
164    $m add -text "Show" -command [itcl::code $this Show]
165    $m add -text "Toggle" -command [itcl::code $this Toggle]
166    $m add -text "Unmap Hidden" -command [itcl::code $this UnmapHidden] \
167        -variable [itcl::scope _unmapHidden] \
168        -type checkbutton
169    $m add -type separator
170    $m add -text "Lower" -command [itcl::code $this Lower]
171    $m add -text "Raise" -command [itcl::code $this Raise]
172    $m add -type separator
173    $m add -text "Average" -command [itcl::code $this Average]
174    $m add -text "Difference" -command [itcl::code $this Difference]
175    $m add -type separator
176    $m add -text "Recolor" -command [itcl::code $this Recolor]
177    $m add -text "Rename" -command [itcl::code $this Rename]
178    $m add -text "Delete" -command [itcl::code $this Delete]
179    set _unmapHidden [$graph cget -unmaphiddenelements]
180    set _graph $graph
181    set cmd [itcl::code $this Toggle current]
182    $itk_component(legend) bind CheckBoxStyle <ButtonRelease-1> \
183        [itcl::code [subst -nocommands {
184            if { [%W edit -root -test %X %Y] } {
185                %W edit -root %X %Y
186                $this Toggle [%W nearest -root %X %Y]
187                break
188            }
189        }]]
190    bind $itk_component(legend) <Enter> { focus %W }
191    $itk_component(legend) bind Entry <Control-KeyRelease-a> \
192        [itcl::code $this SelectAll]
193    $itk_component(legend) bind Entry <KeyRelease-Return> \
194        +[itcl::code $this Toggle focus]
195    $itk_component(legend) bind Entry <Escape> \
196        "$itk_component(legend) selection clearall"
197    $itk_component(legend) configure -selectcommand \
198        [itcl::code $this Check $itk_component(controls)]
199    bind $itk_component(legend) <ButtonRelease-3> \
200        [itcl::code $this PopupMenu %X %Y]
201    itk_component add editor {
202        Rappture::Editor $itk_interior.editor \
203            -activatecommand [itcl::code $this Editor activate] \
204            -validatecommand [itcl::code $this Editor validate] \
205            -applycommand [itcl::code $this Editor apply]
206    }
207    set _lastColorIndex [llength $_autocolors]
208    blt::table $itk_interior \
209        0,0 $itk_interior.mb -anchor w  \
210        1,0 $itk_component(scrollbars) -fill both
211    blt::table configure $itk_interior r0 -resize none
212    Check $itk_component(controls)
213    eval itk_initialize $args
214}
215
216# ----------------------------------------------------------------------
217# DESTRUCTOR
218# ----------------------------------------------------------------------
219itcl::body Rappture::XyLegend::destructor {} {
220    foreach node [$_tree children root] {
221        $_tree delete $node
222    }
223    if { $_diff != "" } {
224        catch { $_graph marker delete $_diff }
225    }
226}
227
228itcl::body Rappture::XyLegend::Add { elem label {flags ""} } {
229    set hide [$_graph element cget $elem -hide]
230    set im [image create picture]
231    $_graph legend icon $elem $im
232    set data(show) $hide
233    set data(delete) [expr { $flags == "-delete" }]
234    set node [$_tree insert root -at 0 -label $elem -data [array get data]]
235    $itk_component(legend) entry configure $node -label $label -icon $im
236    update idletasks
237    return $node
238}
239
240# ----------------------------------------------------------------------
241# USAGE: reset <curve> ?<settings>?
242#
243# Clients use this to add a curve to the plot.  The optional <settings>
244# are used to configure the plot.  Allowed settings are -color,
245# -brightness, -width, -linestyle and -raise.
246# ----------------------------------------------------------------------
247itcl::body Rappture::XyLegend::reset {} {
248    foreach node [$_tree children root] {
249        $_tree delete $node
250    }
251    foreach elem [$_graph element show] {
252        set label [$_graph element cget $elem -label]
253        if { $label == "" } {
254            set label $elem
255        }
256        Add $elem $label
257    }
258    $itk_component(legend) open -recurse root
259    Check $itk_component(controls)
260}
261
262itcl::body Rappture::XyLegend::Hide { args } {
263    if { $args == "" } {
264        set nodes [$itk_component(legend) curselection]
265    } else {
266        set nodes $args
267    }
268    foreach node $nodes {
269        set elem [$_tree label $node]
270        if { ![$_graph element exists $elem] } {
271            continue
272        }
273        $_graph element configure $elem -hide yes
274        $_tree set $node "show" 1
275    }
276}
277
278itcl::body Rappture::XyLegend::Show { args } {
279    if { $args == "" } {
280        set nodes [$itk_component(legend) curselection]
281    } else {
282        set nodes $args
283    }
284    foreach node $nodes {
285        set elem [$_tree label $node]
286        if { ![$_graph element exists $elem] } {
287            continue
288        }
289        $_graph element configure $elem -hide no
290        $_tree set $node "show" 0
291    }
292}
293
294itcl::body Rappture::XyLegend::Toggle { args } {
295    if { $args == "" } {
296        set nodes [$itk_component(legend) curselection]
297    } else {
298        set nodes $args
299    }
300    foreach node $nodes {
301        set elem [$_tree label $node]
302        if { ![$_graph element exists $elem] } {
303            continue
304        }
305        set hide [$_graph element cget $elem -hide]
306        set hide [expr $hide==0]
307        $_tree set $node "show" $hide
308        $_graph element configure $elem -hide $hide
309    }
310}
311
312itcl::body Rappture::XyLegend::Raise { args } {
313    if { $args == "" } {
314        set nodes [$itk_component(legend) curselection]
315    } else {
316        set nodes $args
317    }
318    set elements {}
319    foreach node $nodes {
320        set elem [$_tree label $node]
321        set found($elem) 1
322        set elements [linsert $elements 0 $elem]
323    }
324    foreach elem $elements {
325        $_tree move [$_tree index $elem] 0 -at 0
326    }
327    set list {}
328    foreach elem [$_graph element show] {
329        if { [info exists found($elem)] }  {
330            continue
331        }
332        lappend list $elem
333    }
334    $_graph element show [concat $list $elements]
335}
336
337itcl::body Rappture::XyLegend::Lower { args } {
338    if { $args == "" } {
339        set nodes [$itk_component(legend) curselection]
340    } else {
341        set nodes $args
342    }
343    set elements {}
344    foreach node $nodes {
345        set elem [$_tree label $node]
346        set found($elem) 1
347        set elements [linsert $elements 0 $elem]
348    }
349    set pos [$_tree degree 0]
350
351    foreach elem $elements {
352        incr pos -1
353        $_tree move [$_tree index $elem] 0 -at $pos
354    }
355
356    set list {}
357    foreach elem [$_graph element show] {
358        if { [info exists found($elem)] }  {
359            continue
360        }
361        lappend list $elem
362    }
363    $_graph element show [concat $elements $list]
364}
365
366itcl::body Rappture::XyLegend::Delete { args } {
367    if { $args == "" } {
368        set nodes [$itk_component(legend) curselection]
369    } else {
370        set nodes $args
371    }
372    set elements {}
373    set delnodes {}
374    foreach node $nodes {
375        if { ![$_tree get $node "delete" 0] } {
376            continue
377        }
378        set elem [$_tree label $node]
379        lappend elements $elem
380        lappend delnodes $node
381        if { $_diff != "" && [info exists _diffelements($elem)] } {
382            $_graph marker delete $_diff
383            array unset _diffelements
384            set _diff ""
385        }
386    }
387    if { [llength $delnodes] > 0 } {
388        eval $_tree delete $delnodes
389    }
390    $itk_component(legend) selection clearall
391    eval $_graph element delete $elements
392}
393
394itcl::body Rappture::XyLegend::Check { menu } {
395    set nodes [$itk_component(legend) curselection]
396    $menu item configure all -state disabled
397    $menu item configure "Unmap Hidden" -state normal
398    foreach node $nodes {
399        if { [$_tree get $node "delete" 0] } {
400            $menu item configure Delete -state normal
401            break
402        }
403    }
404    if { [$_tree degree 0] > 1  && [llength $nodes] > 0 } {
405        foreach n { Raise Lower } {
406            $menu item configure $n -state normal
407        }
408    }
409    switch -- [llength $nodes] {
410        0 {
411        }
412        1 {
413            foreach n { Hide Show Toggle Rename Recolor } {
414                $menu item configure $n -state normal
415            }
416        }
417        2 {
418            foreach n { Hide Show Toggle Difference Average Recolor } {
419                $menu item configure $n -state normal
420            }
421        }
422        default {
423            foreach n { Hide Show Toggle Average Recolor } {
424                $menu item configure $n -state normal
425            }
426        }
427    }
428}
429
430itcl::body Rappture::XyLegend::GetData { elem axis } {
431    set data [$_graph element cget $elem $axis]
432
433    foreach { part1 part2} $data break
434    if { [blt::vector names $part1] == $part1 } {
435        return [$part1 values]
436    } elseif { [blt::datatable names $part1] == $part1 } {
437        return [$part1 column values $part2]
438    }
439    # Otherwise assume it's a list of numbers.
440    return $data
441}
442
443itcl::body Rappture::XyLegend::Average {} {
444    set nodes [$itk_component(legend) curselection]
445    if { $nodes == "" } {
446        return
447    }
448    set elements {}
449    set sum [blt::vector create \#auto -command ""]
450
451    set xcoords [blt::vector create \#auto -command ""]
452    set ycoords [blt::vector create \#auto -command ""]
453
454    blt::busy hold $itk_component(hull)
455    update
456    # Step 1. Get the x-values for each curve, then sort them to get the
457    #         unique values.
458
459    set labels {}
460    foreach node $nodes {
461        set elem [$_tree label $node]
462        set label [$_graph element cget $elem -label]
463        $xcoords append [GetData $elem -x]
464        set elements [linsert $elements 0 $elem]
465        set labels [linsert $labels 0 $label]
466    }
467    # Sort the abscissas keeping unique values.
468    $xcoords sort -uniq
469
470    # Step 2. Now for each curve, generate a cubic spline of that curve
471    #         and interpolate to get the corresponding y-values for each
472    #         abscissa.  Normally the abscissa are the same, so we're
473    #         interpolation the knots.
474
475    set x [blt::vector create \#auto -command ""]
476    set y [blt::vector create \#auto -command ""]
477    $sum length [$xcoords length]
478
479    foreach node $nodes {
480        set elem [$_tree label $node]
481        $x set [GetData $elem -x]
482        $y set [GetData $elem -y]
483        blt::spline natural $x $y $xcoords $ycoords
484
485        # Sum the interpolated y-coordinate values.
486        $sum expr "$sum + $ycoords"
487    }
488    blt::vector destroy $x $y
489
490    # Get the average
491    $sum expr "$sum / [llength $elements]"
492
493    # Step 3.  Create a new curve which is the average. Append it to the
494    #          the end.
495
496    set count 0
497    while {[$_graph element exists avg$count] } {
498        incr count
499    }
500    set labels [lsort -dictionary $labels]
501    set name "avg$count"
502    set label "Avg. [join $labels ,]"
503
504    # Don't use the vector because we don't know when it will be cleaned up.
505
506    if { $_lastColorIndex == 0 } {
507        set _lastColorIndex [llength $_autocolors]
508    }
509    incr _lastColorIndex -1
510    set color [lindex $_autocolors $_lastColorIndex]
511    $_graph element create $name -label $label -x [$xcoords values]\
512        -y [$sum values] -symbol scross -pixels 3 -color $color
513    blt::vector destroy $xcoords $ycoords $sum
514    set node [Add $name $label -delete]
515    Raise $node
516    blt::busy forget $itk_component(hull)
517}
518
519itcl::body Rappture::XyLegend::Difference {} {
520
521    if { $_diff != "" } {
522        $_graph marker delete $_diff
523        set _diff ""
524    }
525    set nodes [$itk_component(legend) curselection]
526    set elem1 [$_tree label [lindex $nodes 0]]
527    set elem2 [$_tree label [lindex $nodes 1]]
528    if { [info exists _diffelements($elem1)] &&
529         [info exists _diffelements($elem2)] } {
530        array unset _diffelements;      # Toggle the difference.
531        return;                         
532    }
533    array unset _diffelements
534    set x [blt::vector create \#auto -command ""]
535    set y [blt::vector create \#auto -command ""]
536    set m [blt::vector create \#auto -command ""]
537
538    $x append [GetData $elem1 -x]
539    $y append [GetData $elem1 -y]
540    $x sort -reverse $y
541    $x append [GetData $elem2 -x]
542    $y append [GetData $elem2 -y]
543    $m merge $x $y
544    set _diff [$_graph marker create polygon \
545                   -coords [$m values] \
546                   -element $elem1 \
547                   -stipple dot1 \
548                   -outline "" -fill "#cd69c9"]
549    blt::vector destroy $m $x $y
550    set _diffelements($elem1) 1
551    set _diffelements($elem2) 1
552}
553
554
555itcl::body Rappture::XyLegend::Recolor {} {
556    set nodes [$itk_component(legend) curselection]
557    if { $nodes == "" } {
558        return
559    }
560    foreach node $nodes {
561        set elem [$_tree label $node]
562        if { $_lastColorIndex == 0 } {
563            set _lastColorIndex [llength $_autocolors]
564        }
565        incr _lastColorIndex -1
566        set color [lindex $_autocolors $_lastColorIndex]
567        $_graph element configure $elem -color $color
568        set im [$itk_component(legend) entry cget $node -icon]
569        $_graph legend icon $elem $im
570    }
571}
572
573itcl::body Rappture::XyLegend::UnmapHidden {} {
574    $_graph configure -unmaphiddenelements $_unmapHidden
575}
576
577itcl::body Rappture::XyLegend::SelectAll { } {
578    foreach node [$_tree children 0] {
579        $itk_component(legend) selection set $node
580    } 
581}
582
583itcl::body Rappture::XyLegend::Rename {} {
584    Editor popup
585}
586
587# ----------------------------------------------------------------------
588# USAGE: Editor popup
589# USAGE: Editor activate
590# USAGE: Editor validate <value>
591# USAGE: Editor apply <value>
592# USAGE: Editor menu <rootx> <rooty>
593#
594# Used internally to handle the various functions of the pop-up
595# editor for the value of this gauge.
596# ----------------------------------------------------------------------
597itcl::body Rappture::XyLegend::Editor {option args} {
598    switch -- $option {
599        popup {
600            $itk_component(editor) activate
601        }
602        activate {
603            set _rename [$itk_component(legend) curselection]
604            if { $_rename == "" } {
605                return;
606            }
607            set label [$itk_component(legend) entry cget $_rename -label]
608            foreach { l r w h } [$itk_component(legend) bbox $_rename] break
609            set info(text) $label
610            set info(x) [expr $l + [winfo rootx $itk_component(legend)]]
611            set info(y) [expr $r + [winfo rooty $itk_component(legend)]]
612            set info(w) $w
613            set info(h) $h
614            return [array get info]
615        }
616        validate {
617            if {[llength $args] != 1} {
618                error "wrong # args: should be \"editor validate value\""
619            }
620        }
621        apply {
622            if {[llength $args] != 1} {
623                error "wrong # args: should be \"editor apply value\""
624            }
625            set label [lindex $args 0]
626            $itk_component(legend) entry configure $_rename -label $label
627            set elem [$_tree label $_rename]
628            $_graph element configure $elem -label $label
629        }
630        menu {
631            eval tk_popup $itk_component(emenu) $args
632        }
633        default {
634            error "bad option \"$option\": should be popup, activate, validate, apply, and menu"
635        }
636    }
637}
638
639#
640# BuildPopup --
641#
642#
643itcl::body Rappture::XyLegend::BuildPopup { popup } {
644    set m $popup
645    blt::combomenu $popup \
646        -yscrollbar $m.ys \
647        -xscrollbar $m.xs -height { 0 2.5i }
648    blt::tk::scrollbar $m.xs
649    blt::tk::scrollbar $m.ys
650    $m add -text "Hide" -command [itcl::code $this Hide]
651    $m add -text "Show" -command [itcl::code $this Show]
652    $m add -text "Toggle" -command [itcl::code $this Toggle]
653    $m add -text "Unmap Hidden" -command [itcl::code $this UnmapHidden] \
654        -variable [itcl::scope _unmapHidden] \
655        -type checkbutton
656    $m add -type separator
657    $m add -text "Lower" -command [itcl::code $this Lower]
658    $m add -text "Raise" -command [itcl::code $this Raise]
659    $m add -type separator
660    $m add -text "Average" -command [itcl::code $this Average]
661    $m add -text "Difference" -command [itcl::code $this Difference]
662    $m add -type separator
663    $m add -text "Recolor" -command [itcl::code $this Recolor]
664    $m add -text "Rename" -command [itcl::code $this Rename]
665    $m add -text "Delete" -command [itcl::code $this Delete]
666    set _unmapHidden [$_graph cget -unmaphiddenelements]
667    set cmd [itcl::code $this Toggle current]
668    Check $m
669}
670
671#
672# PopupMenu --
673#
674#       Builds the popup associated with the sensor map.  The menu
675#       is first destroyed if one exists.  This is because the menu
676#       bindings callbacks are specific to the current map.  Destroying
677#       the menu automatically removes the bindings and callbacks.
678#
679itcl::body Rappture::XyLegend::PopupMenu { x y } {
680    set m .xylegendpopup
681    if { [winfo exists $m]  } {
682        destroy $m
683    }
684    BuildPopup $m
685    blt::ComboMenu::popup $m $x $y
686}
Note: See TracBrowser for help on using the repository browser.