source: branches/1.3/gui/scripts/xyresult.tcl @ 4205

Last change on this file since 4205 was 4205, checked in by gah, 7 years ago

bug fix: elem not set if no components in curve

File size: 57.6 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: xyresult - X/Y plot in a ResultSet
4#
5#  This widget is 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 data objects showing on the plot.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
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 *XyResult.width 3i widgetDefault
19option add *XyResult.height 3i widgetDefault
20option add *XyResult.gridColor #d9d9d9 widgetDefault
21option add *XyResult.activeColor blue widgetDefault
22option add *XyResult.dimColor gray widgetDefault
23option add *XyResult.controlBackground gray widgetDefault
24option add *XyResult.font \
25    -*-helvetica-medium-r-normal-*-12-* widgetDefault
26
27set 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
62option add *XyResult.autoColors $autocolors widgetDefault
63option add *XyResult*Balloon*Entry.background white widgetDefault
64
65itcl::class Rappture::XyResult {
66    inherit itk::Widget
67
68    itk_option define -gridcolor gridColor GridColor ""
69    itk_option define -activecolor activeColor ActiveColor ""
70    itk_option define -dimcolor dimColor DimColor ""
71    itk_option define -autocolors autoColors AutoColors ""
72
73    private variable _viewable "";      # Display list for widget.
74    private variable _dispatcher "";    # Dispatcher for !events
75    private variable _dlist "";         # List of dataobj objects
76    private variable _dataobj2raise;    # Maps dataobj => raise flag 0/1
77    private variable _dataobj2desc;     # Maps dataobj => description of data
78    private variable _dataobj2sim;      # Maps dataobj => type of graph element
79    private variable _elem2comp;        # Maps graph element => dataobj
80    private variable _comp2elem;        # Maps graph element => dataobj
81    private variable _label2axis;       # Maps axis label => axis ID
82    private variable _limits;           # Axis limits:  x-min, x-max, etc.
83    private variable _nextColorIndex 0; # Index for next "-color auto"
84    private variable _hilite;           # Info for element currently highlighted
85    private variable _axis;             # Info for axis manipulations
86    private variable _axisPopup;        # Info for axis being edited in popup
87    common _downloadPopup;              # Download options from popup
88    private variable _markers
89    private variable _nextElement 0
90
91    constructor {args} {
92        # defined below
93    }
94    destructor {
95        # defined below
96    }
97    public method add {dataobj {settings ""}}
98    public method get {}
99    public method delete {args}
100    public method scale {args}
101    public method parameters {title args} {
102        # do nothing
103    }
104    public method download {option args}
105
106    protected method Rebuild {}
107    protected method ResetLimits {}
108    protected method ResetLegend {}
109    protected method Zoom {option args}
110    protected method Hilite {state x y}
111    protected method Axis {option args}
112    protected method GetAxes {dataobj}
113    protected method GetLineMarkerOptions { style }
114    protected method GetTextMarkerOptions { style }
115    protected method EnterMarker { g name x y text }
116    protected method LeaveMarker { g name }
117
118
119    private method BuildGraph { dlist }
120    private method BuildMarkers { dataobj elem }
121    private method FormatAxis { axis w value }
122    private method GetFormattedValue { axis g value }
123    private method BuildAxisPopup { popup }
124    private method ShowAxisPopup { axis }
125    private method SetAxis { setting }
126    private method SetElements { dataobj {settings ""} }
127    private method SetAxisRangeState { axis }
128}
129                                                                               
130itk::usual XyResult {
131    keep -background -foreground -cursor -font
132}
133
134itk::usual Panedwindow {
135    keep -background -cursor
136}
137
138# ----------------------------------------------------------------------
139# CONSTRUCTOR
140# ----------------------------------------------------------------------
141itcl::body Rappture::XyResult::constructor {args} {
142    Rappture::dispatcher _dispatcher
143    $_dispatcher register !rebuild
144    $_dispatcher dispatch $this !rebuild "[itcl::code $this Rebuild]; list"
145
146    array set _downloadPopup {
147        format csv
148    }
149
150    option add hull.width hull.height
151    pack propagate $itk_component(hull) no
152
153    itk_component add main {
154        Rappture::SidebarFrame $itk_interior.main
155    }
156    pack $itk_component(main) -expand yes -fill both
157    set f [$itk_component(main) component controls]
158
159    itk_component add reset {
160        button $f.reset -borderwidth 1 -padx 1 -pady 1 \
161            -highlightthickness 0 \
162            -image [Rappture::icon reset-view] \
163            -command [itcl::code $this Zoom reset]
164    } {
165        usual
166        ignore -borderwidth -highlightthickness
167    }
168    pack $itk_component(reset) -padx 4 -pady 2 -anchor e
169    Rappture::Tooltip::for $itk_component(reset) \
170        "Reset the view to the default zoom level"
171
172    set f [$itk_component(main) component frame]
173    itk_component add plot {
174        blt::graph $f.plot \
175            -highlightthickness 0 -plotpadx 0 -plotpady 4
176    } {
177        keep -background -foreground -cursor -font
178    }
179    pack $itk_component(plot) -expand yes -fill both
180
181    $itk_component(plot) pen configure activeLine \
182        -symbol square -pixels 3 -linewidth 2 \
183        -outline black -fill red -color black
184
185    # Add bindings so you can mouse over points to see values:
186    #
187    $itk_component(plot) element bind all <Enter> \
188        [itcl::code $this Hilite at %x %y]
189    $itk_component(plot) element bind all <Motion> \
190        [itcl::code $this Hilite at %x %y]
191    $itk_component(plot) element bind all <Leave> \
192        [itcl::code $this Hilite off %x %y]
193
194    $itk_component(plot) legend configure -hide yes
195
196    #
197    # Add legend for editing hidden/elements:
198    #
199    set inner [$itk_component(main) insert end \
200        -title "Legend" \
201        -icon [Rappture::icon wrench]]
202    $inner configure -borderwidth 4
203
204    itk_component add legend {
205        Rappture::XyLegend $inner.legend $itk_component(plot)
206    }
207    pack $itk_component(legend) -expand yes -fill both
208    after idle [itcl::code $this ResetLegend]
209
210    # quick-and-dirty zoom functionality, for now...
211    Blt_ZoomStack $itk_component(plot)
212    eval itk_initialize $args
213
214    set _hilite(elem) ""
215}
216
217# ----------------------------------------------------------------------
218# DESTRUCTOR
219# ----------------------------------------------------------------------
220itcl::body Rappture::XyResult::destructor {} {
221}
222
223# ----------------------------------------------------------------------
224# USAGE: add <dataobj> ?<settings>?
225#
226# Clients use this to add a dataobj to the plot.  The optional <settings>
227# are used to configure the plot.  Allowed settings are -color,
228# -brightness, -width, -linestyle and -raise.
229# ----------------------------------------------------------------------
230itcl::body Rappture::XyResult::add {dataobj {settings ""}} {
231    set g $itk_component(plot)
232    SetElements $dataobj $settings
233
234    array set attrs $settings
235
236    # Colors have to be set/reset here because of "-brightness" and "auto".
237    # Colors can't be overriden by the user.
238
239    # If the color is "auto", then select a color from -autocolors
240    if { ![info exists attrs(-color)] } {
241        set color "auto"
242    } else {
243        set color $attrs(-color)
244    }
245    if { ![info exists attrs(-width)] } {
246        set linewidth 1
247    } else {
248        set linewidth $attrs(-width)
249    }
250    if { $color == "auto" || $color == "autoreset" } {
251        if { $color == "autoreset" } {
252            set _nextColorIndex 0
253        }
254        set color [lindex $itk_option(-autocolors) $_nextColorIndex]
255        if { "" == $color} {
256            set color black
257        }
258        # Set up for next auto color
259        incr _nextColorIndex
260        if { $_nextColorIndex >= [llength $itk_option(-autocolors)] } {
261            set _nextColorIndex 0
262        }
263    }
264    # If -brightness is set, then update the color.
265    if { [info exists attrs(-brightness)] } {
266        set brightness $attrs(-brightness)
267        set color [Rappture::color::brightness $color $brightness]
268        set bg [$itk_component(plot) cget -plotbackground]
269        foreach {h s v} [Rappture::color::RGBtoHSV $bg] break
270        if {$v > 0.5} {
271            set color [Rappture::color::brightness_max $color 0.8]
272        } else {
273            set color [Rappture::color::brightness_min $color 0.2]
274        }
275    }
276    set type [$dataobj hints type]
277   
278    set elem ""
279    foreach cname [$dataobj components] {
280        set tag $dataobj-$cname
281        set elem $_comp2elem($tag)
282        switch -- $type {
283            "bar" {
284                $g bar configure $elem -foreground $color -background $color \
285                    -hide no
286            }
287            "scatter" {
288                $g line configure $elem -color $color -hide no
289            }
290            default {
291                $g line configure $elem -color $color -hide no \
292                    -linewidth $linewidth
293            }
294        }
295        if { [lsearch $_viewable $elem] < 0 } {
296            lappend _viewable $elem
297        }
298    }
299    if { [$dataobj info class] == "::Rappture::Curve" } {
300        BuildMarkers $dataobj $elem
301    }
302    $_dispatcher event -idle !rebuild
303}
304
305# ----------------------------------------------------------------------
306# USAGE: get
307#
308# Clients use this to query the list of objects being plotted, in
309# order from bottom to top of this result.
310# ----------------------------------------------------------------------
311itcl::body Rappture::XyResult::get {} {
312    # put the dataobj list in order according to -raise options
313    set bottom {}
314    set top {}
315    foreach obj $_dlist {
316        if {[info exists _dataobj2raise($obj)] && $_dataobj2raise($obj)} {
317            lappend top $obj
318        } else {
319            lappend bottom $obj
320        }
321    }
322    set _dlist [concat $bottom $top]
323    return $_dlist
324}
325
326# ----------------------------------------------------------------------
327# USAGE: delete ?<dataobj1> <dataobj2> ...?
328#
329# Clients use this to delete a dataobj from the plot.  If no dataobjs
330# are specified, then all dataobjs are deleted.
331# ----------------------------------------------------------------------
332itcl::body Rappture::XyResult::delete {args} {
333    set g $itk_component(plot)
334
335    # First try to create list of elements from the dataobjs specified
336    set elemlist {}
337    foreach dataobj $args {
338        foreach cname [$dataobj components] {
339            set tag $dataobj-$cname
340            if { [info exists _comp2elem($tag)] } {
341                lappend elemlist $_comp2elem($tag)
342            }
343        }
344    }
345    # If no dataobjs were specified then hide all elements.
346    if { [llength $elemlist] == 0 } {
347        set elemlist [$g element names]
348    }
349    # Hide all elements specified by their dataobjs
350    foreach elem $elemlist {
351        $g element configure $elem -hide yes
352        set i [lsearch $_viewable $elem]
353        if { $i >= 0 } {
354            set _viewable [lreplace $_viewable $i $i]
355        }
356    }
357}
358
359# ----------------------------------------------------------------------
360# USAGE: scale ?<dataobj1> <dataobj2> ...?
361#
362# Sets the default limits for the overall plot according to the
363# limits of the data for all of the given <dataobj> objects.  This
364# accounts for all dataobjs--even those not showing on the screen.
365# Because of this, the limits are appropriate for all dataobjs as
366# the user scans through data in the ResultSet viewer.
367# ----------------------------------------------------------------------
368itcl::body Rappture::XyResult::scale {args} {
369    set _dlist $args
370    BuildGraph $args
371}
372
373# ----------------------------------------------------------------------
374# USAGE: download coming
375# USAGE: download controls <downloadCommand>
376# USAGE: download now
377#
378# Clients use this method to create a downloadable representation
379# of the plot.  Returns a list of the form {ext string}, where
380# "ext" is the file extension (indicating the type of data) and
381# "string" is the data itself.
382# ----------------------------------------------------------------------
383itcl::body Rappture::XyResult::download {option args} {
384    switch $option {
385        coming {
386            # nothing to do
387        }
388        controls {
389            set popup .xyresultdownload
390            if {![winfo exists .xyresultdownload]} {
391                # if we haven't created the popup yet, do it now
392                Rappture::Balloon $popup \
393                    -title "[Rappture::filexfer::label downloadWord] as..."
394                set inner [$popup component inner]
395                label $inner.summary -text "" -anchor w
396                pack $inner.summary -side top
397                radiobutton $inner.csv -text "Data as Comma-Separated Values" \
398                    -variable Rappture::XyResult::_downloadPopup(format) \
399                    -value csv
400                pack $inner.csv -anchor w
401                radiobutton $inner.image -text "Image (PS/PDF/PNG/JPEG)" \
402                    -variable Rappture::XyResult::_downloadPopup(format) \
403                    -value image
404                pack $inner.image -anchor w
405                button $inner.go -text [Rappture::filexfer::label download] \
406                    -command [lindex $args 0]
407                pack $inner.go -side bottom -pady 4
408            } else {
409                set inner [$popup component inner]
410            }
411            set num [llength [get]]
412            set num [expr {($num == 1) ? "1 result" : "$num results"}]
413            $inner.summary configure -text "[Rappture::filexfer::label downloadWord] $num in the following format:"
414            update idletasks ;# fix initial sizes
415            return $popup
416        }
417        now {
418            set popup .xyresultdownload
419            if {[winfo exists .xyresultdownload]} {
420                $popup deactivate
421            }
422            switch -- $_downloadPopup(format) {
423                csv {
424                    # reverse the objects so the selected data appears on top
425                    set dlist ""
426                    foreach dataobj [get] {
427                        set dlist [linsert $dlist 0 $dataobj]
428                    }
429
430                    # generate the comma-separated value data for these objects
431                    set csvdata ""
432                    foreach dataobj $dlist {
433                        append csvdata "[string repeat - 60]\n"
434                        append csvdata " [$dataobj hints label]\n"
435                        if {[info exists _dataobj2desc($dataobj)]
436                            && [llength [split $_dataobj2desc($dataobj) \n]] > 1} {
437                            set indent "for:"
438                            foreach line [split $_dataobj2desc($dataobj) \n] {
439                                append csvdata " $indent $line\n"
440                                set indent "    "
441                            }
442                        }
443                        append csvdata "[string repeat - 60]\n"
444
445                        append csvdata "[$dataobj hints xlabel], [$dataobj hints ylabel]\n"
446                        set first 1
447                        foreach comp [$dataobj components] {
448                            if {!$first} {
449                                # blank line between components
450                                append csvdata "\n"
451                            }
452                            set xv [$dataobj mesh $comp]
453                            set yv [$dataobj values $comp]
454                            foreach x [$xv range 0 end] y [$yv range 0 end] {
455                                append csvdata [format "%20.15g, %20.15g\n" $x $y]
456                            }
457                            set first 0
458                        }
459                        append csvdata "\n"
460                    }
461                    return [list .txt $csvdata]
462                }
463                image {
464                    set popup .xyprintdownload
465                    if { ![winfo exists $popup] } {
466                        # Create a popup for the print dialog
467                        Rappture::Balloon $popup -title "Save as image..."
468                        set inner [$popup component inner]
469                        # Create the print dialog widget and add it to the
470                        # balloon popup.
471                        Rappture::XyPrint $inner.print
472                        $popup configure \
473                            -deactivatecommand [list $inner.print reset]
474                        blt::table $inner 0,0 $inner.print -fill both
475                    }
476                    update
477                    # Activate the popup and call for the output.
478                    foreach { widget toolName plotName } $args break
479                    $popup activate $widget left
480                    set inner [$popup component inner]
481                    set output [$inner.print print $itk_component(plot) \
482                                    $toolName $plotName]
483                    $popup deactivate
484                    return $output
485                }
486            }
487        }
488        default {
489            error "bad option \"$option\": should be coming, controls, now"
490        }
491    }
492}
493
494itcl::body Rappture::XyResult::BuildMarkers { dataobj elem } {
495    set g $itk_component(plot)
496
497    set ymin -Inf
498    set ymax Inf
499    set xmax Inf
500    set xmin -Inf
501    foreach m [$dataobj xmarkers] {
502        foreach {at label style} $m break
503        set id [$g marker create line -coords [list $at $ymin $at $ymax]]
504        $g marker bind $id <Enter> \
505            [itcl::code $this EnterMarker $g x-$label $at $ymin $at]
506        $g marker bind $id <Leave> \
507            [itcl::code $this LeaveMarker $g x-$label]
508        set options [GetLineMarkerOptions $style]
509        $g marker configure $id -element $elem
510        if { $options != "" } {
511            eval $g marker configure $id $options
512        }
513        if { $label != "" } {
514            set id [$g marker create text -anchor nw \
515                        -text $label -coords [list $at $ymax]]
516            $g marker configure $id -element $elem
517            set options [GetTextMarkerOptions $style]
518            if { $options != "" } {
519                eval $g marker configure $id $options
520            }
521        }
522    }
523    foreach m [$dataobj ymarkers] {
524        foreach {at label style} $m break
525        set id [$g marker create line -coords [list $xmin $at $xmax $at]]
526        $g marker configure $id -element $elem
527        $g marker bind $id <Enter> \
528            [itcl::code $this EnterMarker $g $label $at $xmin $at]
529        $g marker bind $id <Leave> \
530            [itcl::code $this LeaveMarker $g $label]
531        set options [GetLineMarkerOptions $style]
532        if { $options != "" } {
533            eval $g marker configure $id $options
534        }
535        if { $label != "" } {
536            set id [$g marker create text -anchor se \
537                        -text $label -coords [list $xmax $at]]
538            $g marker configure $id -element $elem
539            set options [GetTextMarkerOptions $style]
540            if { $options != "" } {
541                eval $g marker configure $id $options
542            }
543        }
544    }
545}
546
547# ----------------------------------------------------------------------
548# USAGE: Rebuild
549#
550#       Called automatically whenever something changes that affects the
551# data in the widget.  Clears any existing data and rebuilds the
552# widget to display new data.
553# ----------------------------------------------------------------------
554itcl::body Rappture::XyResult::Rebuild {} {
555    ResetLegend
556    # Fix raise/lower elements
557}
558
559# ----------------------------------------------------------------------
560# USAGE: ResetLimits
561#
562# Used internally to apply automatic limits to the axes for the
563# current plot.
564# ----------------------------------------------------------------------
565itcl::body Rappture::XyResult::ResetLimits {} {
566    set g $itk_component(plot)
567
568    foreach axis [$g axis names] {
569        $g axis configure $axis -min "" -max ""
570    }
571}
572
573# ----------------------------------------------------------------------
574# USAGE: ResetLegend
575#
576# Used internally to apply automatic limits to the axes for the
577# current plot.
578# ----------------------------------------------------------------------
579itcl::body Rappture::XyResult::ResetLegend {} {
580    update idletasks
581
582    set g $itk_component(plot)
583    # Fix duplicate labels by appending the simulation number
584    # Collect the labels from all the viewable elements.
585    set above {}
586    set below {}
587    foreach elem $_viewable {
588        foreach {dataobj cname} [split $_elem2comp($elem) -] break
589        set label [$dataobj hints label]
590        lappend label2elem($label) $elem
591        if { $_dataobj2raise($dataobj) } {
592            lappend $g element raise $elem
593        }
594    }
595    # Then relabel elements with the same label, using the simulation number.
596    foreach label [array names label2elem] {
597        foreach elem $label2elem($label) {
598            if { [llength $label2elem($label)] == 1 } {
599                $g element configure $elem -label $label
600                continue
601            }
602            foreach {dataobj cname} [split $_elem2comp($elem) -] break
603            set sim $_dataobj2sim($dataobj)
604            set elabel [format "%s \#%d" $label $sim]
605            $g element configure $elem -label $elabel
606        }
607    }       
608    $itk_component(legend) reset $_viewable
609}
610
611
612# ----------------------------------------------------------------------
613# USAGE: Zoom reset
614#
615# Called automatically when the user clicks on one of the zoom
616# controls for this widget.  Changes the zoom for the current view.
617# ----------------------------------------------------------------------
618itcl::body Rappture::XyResult::Zoom {option args} {
619    switch -- $option {
620        reset {
621            ResetLimits
622            Rappture::Logger::log curve zoom -reset
623        }
624    }
625}
626
627# ----------------------------------------------------------------------
628# USAGE: Hilite <state> <x> <y>
629#
630# Called automatically when the user brushes one of the elements
631# on the plot.  Causes the element to highlight and a tooltip to
632# pop up with element info.
633# ----------------------------------------------------------------------
634itcl::body Rappture::XyResult::Hilite {state x y} {
635    set g $itk_component(plot)
636    set elem ""
637 
638    # Peek inside of Blt_ZoomStack package to see if we're currently in the
639    # middle of a zoom selection.
640    if {[info exists ::zoomInfo($g,corner)] && $::zoomInfo($g,corner) == "B" } {
641        return;
642    }
643    set tip ""
644    if {$state == "at"} {
645        if {[$g element closest $x $y info -interpolate yes]} {
646            # for dealing with xy line plots
647            set elem $info(name)
648
649            set mapx [$g element cget $elem -mapx]
650            set mapy [$g element cget $elem -mapy]
651
652            # search again for an exact point -- this time don't interpolate
653            set tip ""
654            array unset info
655            if {[$g element closest $x $y info -interpolate no]
656                  && $info(name) == $elem} {
657
658                set x [$g axis transform $mapx $info(x)]
659                set y [$g axis transform $mapy $info(y)]
660                if {[info exists _elem2comp($elem)]} {
661                    foreach {dataobj cname} [split $_elem2comp($elem) -] break
662                    set yunits [$dataobj hints yunits]
663                    set xunits [$dataobj hints xunits]
664                } else {
665                    set xunits ""
666                    set yunits ""
667                }
668                set tip [$g element cget $elem -label]
669                set yval [GetFormattedValue y $g $info(y)]
670                append tip "\n$yval$yunits"
671                set xval [GetFormattedValue x $g $info(x)]
672                append tip " @ $xval$xunits"
673                set tip [string trim $tip]
674            }
675            set state 1
676        } elseif {[$g element closest $x $y info -interpolate no]} {
677            # for dealing with xy scatter plot
678            set elem $info(name)
679
680            set mapx [$g element cget $elem -mapx]
681            set mapy [$g element cget $elem -mapy]
682
683            set tip ""
684            set x [$g axis transform $mapx $info(x)]
685            set y [$g axis transform $mapy $info(y)]
686               
687            if {[info exists _elem2comp($elem)]} {
688                foreach {dataobj cname} [split $_elem2comp($elem) -] break
689                set yunits [$dataobj hints yunits]
690                set xunits [$dataobj hints xunits]
691            } else {
692                set xunits ""
693                set yunits ""
694            }
695            set tip [$g element cget $elem -label]
696            set yval [GetFormattedValue y $g $info(y)]
697            append tip "\n$yval$yunits"
698            set xval [GetFormattedValue x $g $info(x)]
699            append tip " @ $xval$xunits"
700            set tip [string trim $tip]
701            set state 1
702        } else {
703            set state 0
704        }
705    }
706
707    if {$state} {
708        #
709        # Highlight ON:
710        # - activate trace
711        # - multiple axes? dim other axes
712        # - pop up tooltip about data
713        #
714        if { [$g element exists $_hilite(elem)] && $_hilite(elem) != $elem } {
715            $g element deactivate $_hilite(elem)
716            $g crosshairs configure -hide yes
717            Rappture::Tooltip::tooltip cancel
718        }
719        $g element activate $elem
720        set _hilite(elem) $elem
721
722        set mapx [$g element cget $elem -mapx]
723        set mapy [$g element cget $elem -mapy]
724        if {[info exists _elem2comp($elem)]} {
725            foreach {dataobj cname} [split $_elem2comp($elem) -] break
726            foreach {mapx mapy} [GetAxes $dataobj] break
727        }
728        set allx [$g x2axis use]
729        if {[llength $allx] > 0} {
730            lappend allx x  ;           # fix main x-axis too
731            foreach axis $allx {
732                if {$axis == $mapx} {
733                    $g axis configure $axis -color $itk_option(-foreground) \
734                        -titlecolor $itk_option(-foreground)
735                } else {
736                    $g axis configure $axis -color $itk_option(-dimcolor) \
737                        -titlecolor $itk_option(-dimcolor)
738                }
739            }
740        }
741        set ally [$g y2axis use]
742        if {[llength $ally] > 0} {
743            lappend ally y  ;           # fix main y-axis too
744            foreach axis $ally {
745                if {$axis == $mapy} {
746                    $g axis configure $axis -color $itk_option(-foreground) \
747                        -titlecolor $itk_option(-foreground)
748                } else {
749                    $g axis configure $axis -color $itk_option(-dimcolor) \
750                        -titlecolor $itk_option(-dimcolor)
751                }
752            }
753        }
754        if {"" != $tip} {
755            $g crosshairs configure -hide no -position @$x,$y
756
757            if {$x > 0.5*[winfo width $g]} {
758                if {$x < 4} {
759                    set tipx "-0"
760                } else {
761                    set tipx "-[expr {$x-20}]"  ;# move tooltip to the left
762                }
763            } else {
764                if {$x < -4} {
765                    set tipx "+0"
766                } else {
767                    set tipx "+[expr {$x+20}]"  ;# move tooltip to the right
768                }
769            }
770            if {$y > 0.5*[winfo height $g]} {
771                if {$y < 4} {
772                    set tipy "-0"
773                } else {
774                    set tipy "-[expr {$y-20}]"  ;# move tooltip to the top
775                }
776            } else {
777                if {$y < -4} {
778                    set tipy "+0"
779                } else {
780                    set tipy "+[expr {$y+20}]"  ;# move tooltip to the bottom
781                }
782            }
783            Rappture::Tooltip::text $g $tip
784            Rappture::Tooltip::tooltip show $g $tipx,$tipy
785            Rappture::Logger::log tooltip -for "curve probe -- [string map [list \n " // "] $tip]"
786        }
787    } else {
788        #
789        # Highlight OFF:
790        # - deactivate (color back to normal)
791        # - put all axes back to normal color
792        # - take down tooltip
793        #
794        if { [$g element exists $_hilite(elem)] } {
795            $g element deactivate $_hilite(elem)
796        }
797        set allx [$g x2axis use]
798        if {[llength $allx] > 0} {
799            lappend allx x  ;# fix main x-axis too
800            foreach axis $allx {
801                $g axis configure $axis -color $itk_option(-foreground) \
802                    -titlecolor $itk_option(-foreground)
803            }
804        }
805       
806        set ally [$g y2axis use]
807        if {[llength $ally] > 0} {
808            lappend ally y  ;# fix main y-axis too
809            foreach axis $ally {
810                $g axis configure $axis -color $itk_option(-foreground) \
811                    -titlecolor $itk_option(-foreground)
812            }
813        }
814
815        $g crosshairs configure -hide yes
816
817        # only cancel in plotting area or we'll mess up axes
818        if {[$g inside $x $y]} {
819            Rappture::Tooltip::tooltip cancel
820        }
821
822        # There is no currently highlighted element
823        set _hilite(elem) ""
824    }
825}
826
827# ----------------------------------------------------------------------
828# USAGE: Axis hilite <axis> <state>
829#
830# USAGE: Axis click <axis> <x> <y>
831# USAGE: Axis drag <axis> <x> <y>
832# USAGE: Axis release <axis> <x> <y>
833#
834# Used internally to handle editing of the x/y axes.  The hilite
835# operation causes the axis to light up.  The edit operation pops
836# up a panel with editing options.  The changed operation applies
837# changes from the panel.
838# ----------------------------------------------------------------------
839itcl::body Rappture::XyResult::Axis {option args} {
840    switch -- $option {
841        hilite {
842            if {[llength $args] != 2} {
843                error "wrong # args: should be \"Axis hilite axis state\""
844            }
845            set g $itk_component(plot)
846            set axis [lindex $args 0]
847            set state [lindex $args 1]
848
849            if {$state} {
850                $g axis configure $axis \
851                    -color $itk_option(-activecolor) \
852                    -titlecolor $itk_option(-activecolor)
853
854                set x [expr {[winfo pointerx $g]+4}]
855                set y [expr {[winfo pointery $g]+4}]
856                Rappture::Tooltip::tooltip pending $g-$axis @$x,$y
857            } else {
858                $g axis configure $axis \
859                    -color $itk_option(-foreground) \
860                    -titlecolor $itk_option(-foreground)
861                Rappture::Tooltip::tooltip cancel
862            }
863        }
864        click {
865            if {[llength $args] != 3} {
866                error "wrong # args: should be \"Axis click axis x y\""
867            }
868            set axis [lindex $args 0]
869            set x [lindex $args 1]
870            set y [lindex $args 2]
871            set g $itk_component(plot)
872
873            set _axis(moved) 0
874            set _axis(click-x) $x
875            set _axis(click-y) $y
876            foreach {min max} [$g axis limits $axis] break
877            set _axis(min0) $min
878            set _axis(max0) $max
879            Rappture::Tooltip::tooltip cancel
880        }
881        drag {
882            if {[llength $args] != 3} {
883                error "wrong # args: should be \"Axis drag axis x y\""
884            }
885            if {![info exists _axis(moved)]} {
886                return  ;# must have skipped click event -- ignore
887            }
888            set axis [lindex $args 0]
889            set x [lindex $args 1]
890            set y [lindex $args 2]
891            set g $itk_component(plot)
892
893            if {[info exists _axis(click-x)] && [info exists _axis(click-y)]} {
894                foreach {x0 y0 pw ph} [$g extents plotarea] break
895                switch -glob $axis {
896                  x* {
897                    set pix $x
898                    set pix0 $_axis(click-x)
899                    set pixmin $x0
900                    set pixmax [expr {$x0+$pw}]
901                  }
902                  y* {
903                    set pix $y
904                    set pix0 $_axis(click-y)
905                    set pixmin [expr {$y0+$ph}]
906                    set pixmax $y0
907                  }
908                }
909                set log [$g axis cget $axis -logscale]
910                set min $_axis(min0)
911                set max $_axis(max0)
912                set dpix [expr {abs($pix-$pix0)}]
913                set v0 [$g axis invtransform $axis $pixmin]
914                set v1 [$g axis invtransform $axis [expr {$pixmin+$dpix}]]
915                if {$log} {
916                    set v0 [expr {log10($v0)}]
917                    set v1 [expr {log10($v1)}]
918                    set min [expr {log10($min)}]
919                    set max [expr {log10($max)}]
920                }
921
922                if {$pix > $pix0} {
923                    set delta [expr {$v1-$v0}]
924                } else {
925                    set delta [expr {$v0-$v1}]
926                }
927                set min [expr {$min-$delta}]
928                set max [expr {$max-$delta}]
929                if {$log} {
930                    set min [expr {pow(10.0,$min)}]
931                    set max [expr {pow(10.0,$max)}]
932                }
933                $g axis configure $axis -min $min -max $max
934
935                # move axis, don't edit on release
936                set _axis(move) 1
937            }
938        }
939        release {
940            if {[llength $args] != 3} {
941                error "wrong # args: should be \"Axis release axis x y\""
942            }
943            if {![info exists _axis(moved)]} {
944                return  ;# must have skipped click event -- ignore
945            }
946            set axis [lindex $args 0]
947            set x [lindex $args 1]
948            set y [lindex $args 2]
949
950            if {!$_axis(moved)} {
951                # small movement? then treat as click -- pop up axis editor
952                set dx [expr {abs($x-$_axis(click-x))}]
953                set dy [expr {abs($y-$_axis(click-y))}]
954                if {$dx < 2 && $dy < 2} {
955                    ShowAxisPopup $axis
956                    return
957                }
958            }
959            # one last movement
960            Axis drag $axis $x $y
961
962            # log this change
963            Rappture::Logger::log curve axis $axis \
964                -drag [$itk_component(plot) axis limits $axis]
965
966            catch {unset _axis}
967        }
968        default {
969            error "bad option \"$option\": should be hilite"
970        }
971    }
972}
973
974
975# ----------------------------------------------------------------------
976# USAGE: GetLineMarkerOptions <style>
977#
978# Used internally to create a list of configuration options specific to the
979# axis line marker.  The input is a list of name value pairs.  Options that
980# are not recognized are ignored.
981# ----------------------------------------------------------------------
982itcl::body Rappture::XyResult::GetLineMarkerOptions {style} {
983    array set lineOptions {
984        "-color"  "-outline"
985        "-dashes" "-dashes"
986        "-linecolor" "-outline"
987        "-linewidth" "-linewidth"
988    }
989    set options {}
990    foreach {name value} $style {
991        if { [info exists lineOptions($name)] } {
992            lappend options $lineOptions($name) $value
993        }
994    }
995    return $options
996}
997
998# ----------------------------------------------------------------------
999# USAGE: GetTextMarkerOptions <style>
1000#
1001# Used internally to create a list of configuration options specific to the
1002# axis text marker.  The input is a list of name value pairs.  Options that
1003# are not recognized are ignored.
1004# ----------------------------------------------------------------------
1005itcl::body Rappture::XyResult::GetTextMarkerOptions {style} {
1006    array set textOptions {
1007        "-color"        "-outline"
1008        "-textcolor"    "-outline"
1009        "-font"         "-font"
1010        "-xoffset"      "-xoffset"
1011        "-yoffset"      "-yoffset"
1012        "-anchor"       "-anchor"
1013        "-rotate"       "-rotate"
1014    }
1015    set options {}
1016    foreach {name value} $style {
1017        if { [info exists textOptions($name)] } {
1018            lappend options $textOptions($name) $value
1019        }
1020    }
1021    return $options
1022}
1023
1024# ----------------------------------------------------------------------
1025# USAGE: GetAxes <dataobj>
1026#
1027# Used internally to figure out the axes used to plot the given
1028# <dataobj>.  Returns a list of the form {x y}, where x is the
1029# x-axis name (x, x2, x3, etc.), and y is the y-axis name.
1030# ----------------------------------------------------------------------
1031itcl::body Rappture::XyResult::GetAxes {dataobj} {
1032    set xlabel [$dataobj hints xlabel]
1033    if {[info exists _label2axis($xlabel)]} {
1034        set mapx $_label2axis($xlabel)
1035    } else {
1036        set mapx "x"
1037    }
1038
1039    set ylabel [$dataobj hints ylabel]
1040    if {[info exists _label2axis($ylabel)]} {
1041        set mapy $_label2axis($ylabel)
1042    } else {
1043        set mapy "y"
1044    }
1045    return [list $mapx $mapy]
1046}
1047
1048# ----------------------------------------------------------------------
1049# CONFIGURATION OPTION: -gridcolor
1050# ----------------------------------------------------------------------
1051itcl::configbody Rappture::XyResult::gridcolor {
1052    if {"" == $itk_option(-gridcolor)} {
1053        $itk_component(plot) grid off
1054    } else {
1055        $itk_component(plot) grid configure -color $itk_option(-gridcolor)
1056        $itk_component(plot) grid on
1057    }
1058}
1059
1060# ----------------------------------------------------------------------
1061# CONFIGURATION OPTION: -autocolors
1062# ----------------------------------------------------------------------
1063itcl::configbody Rappture::XyResult::autocolors {
1064    foreach c $itk_option(-autocolors) {
1065        if {[catch {winfo rgb $itk_component(hull) $c}]} {
1066            error "bad color \"$c\""
1067        }
1068    }
1069    if {$_nextColorIndex >= [llength $itk_option(-autocolors)]} {
1070        set _nextColorIndex 0
1071    }
1072}
1073
1074itcl::body Rappture::XyResult::EnterMarker { g name x y text } {
1075    LeaveMarker $g $name
1076    set id [$g marker create text \
1077                -coords [list $x $y] \
1078                -yoffset -1 \
1079                -anchor s \
1080                -text $text]
1081    set _markers($name) $id
1082}
1083
1084itcl::body Rappture::XyResult::LeaveMarker { g name } {
1085    if { [info exists _markers($name)] } {
1086        set id $_markers($name)
1087        $g marker delete $id
1088        unset _markers($name)
1089    }
1090}
1091
1092#
1093# SetAxis --
1094#
1095#       Configures the graph axis with the designated setting using
1096#       the currently stored value.  User-configurable axis settings
1097#       are stored in the _axisPopup variable or in the widgets. This
1098#       routine syncs the graph with that setting.
1099#
1100itcl::body Rappture::XyResult::SetAxis { setting } {
1101    set g $itk_component(plot)
1102    set axis $_axisPopup(axis)
1103    switch -- $setting {
1104        "logscale" {
1105            set bool $_axisPopup(logscale)
1106            $g axis configure $axis -logscale $bool
1107        }
1108        "loose" {
1109            set bool $_axisPopup(loose)
1110            $g axis configure $axis -loose $bool
1111        }
1112        "range" {
1113            set auto $_axisPopup(auto)
1114            set _axisPopup($axis-auto) $auto
1115            if { $auto } {
1116                # Set the axis range automatically
1117                $g axis configure $axis -min "" -max ""
1118            } else {
1119                # Set the axis range from the entry values.
1120                set label $_axisPopup(label)
1121                set min $_axisPopup(${label}-min)
1122                set max $_axisPopup(${label}-max)
1123                $g axis configure $axis -min $min -max $max
1124            }
1125            SetAxisRangeState $axis
1126        }
1127        "format" {
1128            set inner [$itk_component(hull).axes component inner]
1129            set format [$inner.format translate [$inner.format value]]
1130            set _axisPopup($axis-format) $format
1131
1132            # Force the graph to reformat the ticks
1133            set min [$itk_component(plot) axis cget $axis -min]
1134            $g axis configure $axis -min $min
1135        }
1136        "label" {
1137            set label $_axisPopup(label)
1138            $g axis configure $axis -title $label
1139        }
1140        "min" {
1141            set min $_axisPopup(min)
1142            if { [catch { $g axis configure $axis -min $min } msg] != 0 } {
1143                set inner [$itk_component(hull).axes component inner]
1144                Rappture::Tooltip::cue $inner.max $msg
1145                bell
1146                return
1147            }
1148            set label $_axisPopup(label)
1149            set _axisPopup(${label}-min) $min
1150        }
1151        "max" {
1152            set max $_axisPopup(max)
1153            if { [catch { $g axis configure $axis -max $max } msg] != 0 } {
1154                set inner [$itk_component(hull).axes component inner]
1155                Rappture::Tooltip::cue $inner.max $msg
1156                bell
1157                return
1158            }
1159            set label $_axisPopup(label)
1160            set _axisPopup(${label}-max) $max
1161        }
1162    }
1163}
1164
1165#
1166# SetAxisRangeState --
1167#
1168#       Sets the state of widgets controlling the axis range based
1169#       upon whether the automatic or manual setting.  If the
1170#       axis is configure to be automatic, the manual setting widgets
1171#       are disabled.  And vesa-versa the automatic setting widgets
1172#       are dsiabled if the axis is manual.
1173#
1174itcl::body Rappture::XyResult::SetAxisRangeState { axis } {
1175    set inner [$itk_component(hull).axes component inner]
1176    set g $itk_component(plot)
1177
1178    if { $_axisPopup(auto) } {
1179        foreach {min max} [$g axis limits $axis] break
1180        $inner.minl configure -state disabled
1181        $inner.min configure -state disabled
1182        $inner.maxl configure -state disabled
1183        $inner.max configure -state disabled
1184        $inner.loose configure -state normal
1185        $inner.tight configure -state normal
1186    } else {
1187        foreach {min max} [$g axis limits $axis] break
1188        $inner.minl configure -state normal
1189        $inner.min configure -state normal
1190        set _axisPopup(min) [$g axis cget $axis -min]
1191        $inner.maxl configure -state normal
1192        $inner.max configure -state normal
1193        set _axisPopup(max) [$g axis cget $axis -max]
1194        $inner.loose configure -state disabled
1195        $inner.tight configure -state disabled
1196    }
1197}
1198
1199#
1200# BuildAxisPopup --
1201#
1202#       Creates the popup balloon dialog for axes. This routine is
1203#       called only once the first time the user clicks to bring up
1204#       an axis dialog.  It is reused for all other axes. 
1205#
1206itcl::body Rappture::XyResult::BuildAxisPopup { popup } {
1207    Rappture::Balloon $popup -title "Axis Options"
1208    set inner [$itk_component(hull).axes component inner]
1209
1210    label $inner.labell -text "Label:"
1211    entry $inner.label \
1212        -width 15 -highlightbackground $itk_option(-background) \
1213        -textvariable [itcl::scope _axisPopup(label)]
1214
1215    bind $inner.label <Return>   [itcl::code $this SetAxis label]
1216    bind $inner.label <KP_Enter> [itcl::code $this SetAxis label]
1217    bind $inner.label <FocusOut> [itcl::code $this SetAxis label]
1218
1219    label $inner.formatl -text "Format:"
1220    Rappture::Combobox $inner.format -width 15 -editable no
1221    $inner.format choices insert end \
1222        "%.6g"  "Auto"         \
1223        "%.0f"  "X"          \
1224        "%.1f"  "X.X"          \
1225        "%.2f"  "X.XX"         \
1226        "%.3f"  "X.XXX"        \
1227        "%.6f"  "X.XXXXXX"     \
1228        "%.1e"  "X.Xe+XX"      \
1229        "%.2e"  "X.XXe+XX"     \
1230        "%.3e"  "X.XXXe+XX"    \
1231        "%.6e"  "X.XXXXXXe+XX"
1232
1233    bind $inner.format <<Value>> [itcl::code $this SetAxis format]
1234
1235    label $inner.rangel -text "Axis Range:"
1236    radiobutton $inner.auto -text "Automatic" \
1237        -variable [itcl::scope _axisPopup(auto)] -value 1 \
1238        -command [itcl::code $this SetAxis range]
1239    radiobutton $inner.manual -text "Manual" \
1240        -variable [itcl::scope _axisPopup(auto)] -value 0 \
1241        -command [itcl::code $this SetAxis range]
1242
1243    radiobutton $inner.loose -text "loose" \
1244        -variable [itcl::scope _axisPopup(loose)] -value 1 \
1245        -command [itcl::code $this SetAxis loose]
1246    radiobutton $inner.tight -text "tight" \
1247        -variable [itcl::scope _axisPopup(loose)] -value 0 \
1248        -command [itcl::code $this SetAxis loose]
1249
1250    label $inner.minl -text "min"
1251    entry $inner.min \
1252        -width 15 -highlightbackground $itk_option(-background) \
1253        -textvariable [itcl::scope _axisPopup(min)]
1254    bind $inner.min <Return> [itcl::code $this SetAxis min]
1255    bind $inner.min <KP_Enter> [itcl::code $this SetAxis min]
1256    bind $inner.min <FocusOut> [itcl::code $this SetAxis min]
1257
1258    label $inner.maxl -text "max"
1259    entry $inner.max \
1260        -width 15 -highlightbackground $itk_option(-background) \
1261        -textvariable [itcl::scope _axisPopup(max)]
1262    bind $inner.max <Return> [itcl::code $this SetAxis max]
1263    bind $inner.max <KP_Enter> [itcl::code $this SetAxis max]
1264    bind $inner.max <FocusOut> [itcl::code $this SetAxis max]
1265
1266
1267    label $inner.scalel -text "Scale:"
1268    radiobutton $inner.linear -text "linear" \
1269        -variable [itcl::scope _axisPopup(logscale)] -value 0 \
1270        -command [itcl::code $this SetAxis logscale]
1271    radiobutton $inner.log -text "logarithmic" \
1272        -variable [itcl::scope _axisPopup(logscale)] -value 1 \
1273        -command [itcl::code $this SetAxis logscale]
1274
1275    blt::table $inner \
1276        0,0 $inner.labell -anchor w \
1277        0,1 $inner.label -anchor w -fill x  -cspan 3 \
1278        1,0 $inner.formatl -anchor w \
1279        1,1 $inner.format -anchor w -fill x  -cspan 3 \
1280        2,0 $inner.scalel -anchor w \
1281        2,2 $inner.linear -anchor w \
1282        2,3 $inner.log -anchor w \
1283        3,0 $inner.rangel -anchor w \
1284        4,0 $inner.manual -anchor w -padx 4 \
1285        4,2 $inner.minl -anchor e \
1286        4,3 $inner.min -anchor w \
1287        5,2 $inner.maxl -anchor e \
1288        5,3 $inner.max -anchor w \
1289        6,0 $inner.auto -anchor w -padx 4 \
1290        6,2 $inner.tight -anchor w \
1291        6,3 $inner.loose -anchor w \
1292       
1293
1294    blt::table configure $inner r2 -pady 4
1295    blt::table configure $inner c1 -width 20
1296    update
1297}
1298
1299#
1300# ShowAxisPopup --
1301#
1302#       Displays the axis dialog for an axis.  It initializes the
1303#       _axisInfo variables for that axis if necessary.
1304#
1305itcl::body Rappture::XyResult::ShowAxisPopup { axis } {
1306    set g $itk_component(plot)
1307    set popup $itk_component(hull).axes
1308
1309    if { ![winfo exists $popup] } {
1310        BuildAxisPopup $popup
1311    }
1312    set _axisPopup(axis)     $axis
1313    set _axisPopup(label)    [$g axis cget $axis -title]
1314    set _axisPopup(logscale) [$g axis cget $axis -logscale]
1315    set _axisPopup(loose)    [$g axis cget $axis -loose]
1316    if { ![info exists _axisPopup($axis-format)] } {
1317        set inner [$itk_component(hull).axes component inner]
1318        set _axisPopup($axis-format) "%.6g"
1319        set fmts [$inner.format choices get -value]
1320        set i [lsearch -exact $fmts $_axisPopup($axis-format)]
1321        if {$i < 0} { set i 0 }  ;# use Auto choice
1322        $inner.format value [$inner.format choices get -label $i]
1323    }
1324    foreach {min max} [$g axis limits $axis] break
1325    if { $_axisPopup(logscale) } {
1326        set type "log"
1327    } else {
1328        set type "lin"
1329    }
1330    set amin ""
1331    set label $_axisPopup(label)
1332    if { [info exists _limits(${label}-min)] } {
1333        set amin $_limits(${label}-min)
1334    }
1335    set amax ""
1336    if { [info exists _limits(${label}-max)] } {
1337        set amax $_limits(${label}-max)
1338    }
1339    set auto 1
1340    if { $amin != "" || $amax != "" } {
1341        set auto 0
1342    }
1343    if { ![info exists _axisPopup($axis-auto)] } {
1344        set _axisPopup($axis-auto) $auto;# Defaults to automatic
1345    }
1346    set _axisPopup(auto)  $_axisPopup($axis-auto)
1347    SetAxisRangeState $axis
1348    if { ![info exists _axisPopup(${label}-min)] } {
1349        if { $amin != "" } {
1350            set _axisPopup(${label}-min) $amin
1351            set _axisPopup(min)   $_axisPopup(${label}-min)
1352            SetAxis min
1353        } else {
1354            set _axisPopup(${label}-min) $min
1355        }
1356    }
1357    if { ![info exists _axisPopup(${label}-max)] } {
1358        if { $amax != "" } {
1359            set _axisPopup(${label}-max) $amax
1360            set _axisPopup(max)   $_axisPopup(${label}-max)
1361            SetAxis max
1362        } else {
1363            set _axisPopup(${label}-max) $max
1364        }
1365    }
1366    set _axisPopup(min)  $_axisPopup(${label}-min)
1367    set _axisPopup(max)  $_axisPopup(${label}-max)
1368    set _axisPopup(axis) $axis
1369
1370    #
1371    # Figure out where the window should pop up.
1372    #
1373    set x [winfo rootx $g]
1374    set y [winfo rooty $g]
1375    set w [winfo width $g]
1376    set h [winfo height $g]
1377    foreach {x0 y0 pw ph} [$g extents plotarea] break
1378    switch -glob -- $axis {
1379        x {
1380            set x [expr {round($x + $x0+0.5*$pw)}]
1381            set y [expr {round($y + $y0+$ph + 0.5*($h-$y0-$ph))}]
1382            set dir "above"
1383        }
1384        x* {
1385            set x [expr {round($x + $x0+0.5*$pw)}]
1386            set dir "below"
1387            set allx [$itk_component(plot) x2axis use]
1388            set max [llength $allx]
1389            set i [lsearch -exact $allx $axis]
1390            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1391        }
1392        y {
1393            set x [expr {round($x + 0.5*$x0)}]
1394            set y [expr {round($y + $y0+0.5*$ph)}]
1395            set dir "right"
1396        }
1397        y* {
1398            set y [expr {round($y + $y0+0.5*$ph)}]
1399            set dir "left"
1400            set ally [$g y2axis use]
1401            set max [llength $ally]
1402            set i [lsearch -exact $ally $axis]
1403            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1404            set x [expr {round($x+$x0+$pw + ($i+0.5)*($w-$x0-$pw)/double($max))}]
1405        }
1406    }
1407    $popup activate @$x,$y $dir
1408}
1409
1410#
1411# GetFormattedValue --
1412#
1413#       Callback routine for the axis format procedure.  It formats the
1414#       axis tick label according to the selected format.  This routine
1415#       is also used to format tooltip values.
1416#
1417itcl::body Rappture::XyResult::GetFormattedValue { axis g value } {
1418    if { [$g axis cget $axis -logscale] ||
1419         ![info exists _axisPopup($axis-format)] } {
1420        set fmt "%.6g"
1421    } else {
1422        set fmt $_axisPopup($axis-format)
1423    }
1424    return [format $fmt $value]
1425}
1426
1427
1428#
1429# BuildGraph --
1430#
1431#       This procedure loads each data objects specified into the
1432#       graph.  The data object may already be loaded (from the "add"
1433#       method which gets called first).   The graph elements that
1434#       are created, are hidden.  This allows the graph to account
1435#       for all datasets, even those not currently being displayed.
1436#       
1437itcl::body Rappture::XyResult::BuildGraph { dlist } {
1438    set g $itk_component(plot)
1439   
1440    foreach label [array names _label2axis] {
1441        set axis $_label2axis($label)
1442        switch -- $axis {
1443            "x" - "x2" - "y" - "y2" {
1444                $g axis configure $axis -hide yes -checklimits no
1445            }
1446            default {
1447                $g axis delete $axis
1448            }
1449        }
1450    }
1451    array unset _label2axis
1452    array unset _limits
1453
1454    # Scan through all objects and create a list of all axes.
1455    # The first x-axis gets mapped to "x".  The second, to "x2".
1456    # Beyond that, we must create new axes "x3", "x4", etc.
1457    # We do the same for y.
1458   
1459    set anum(x) 0
1460    set anum(y) 0
1461    foreach dataobj $dlist {
1462        foreach axis {x y} {
1463            set label [$dataobj hints ${axis}label]
1464            if { $label == "" } {
1465                continue
1466            }
1467            # Collect the limits (if set for the axis)
1468            set min [$dataobj hints ${axis}min]
1469            set max [$dataobj hints ${axis}max]
1470            set tag ${label}-min
1471            if { $min != "" && ( ![info exists _limits($tag)] ||
1472                                   $_limits($tag) > $min ) } {
1473                set _limits($tag) $min
1474            }
1475            set tag ${label}-max
1476            if { $max != "" && (![info exists _limits($tag)] ||
1477                                  $_limits($tag) < $max) } {
1478                set _limits($tag) $max
1479            }
1480            if  { [$dataobj hints ${axis}scale] == "log" } {
1481                set tag ${label}-log
1482                set _limits($tag) 1
1483            }
1484            if { ![info exists _label2axis($label)] } {
1485                switch [incr anum($axis)] {
1486                    1 { set axisName $axis }
1487                    2 { set axisName ${axis}2 }
1488                    default {
1489                        set axis $axis$anum($axis)
1490                        catch {$g axis create $axisName}
1491                    }
1492                }
1493                $g axis configure $axisName -title $label -hide no \
1494                    -checklimits no -showticks yes
1495                set _label2axis($label) $axisName
1496               
1497                # If this axis has a description, add it as a tooltip
1498                set desc [string trim [$dataobj hints ${axis}desc]]
1499                Rappture::Tooltip::text $g-$axisName $desc
1500            }
1501        }
1502    }
1503    # Next, set the axes based on what we've found.
1504    foreach label [array names _label2axis] {
1505        set logscale [info exists _limits(${label}-log)]
1506        set amin ""
1507        if { [info exists _limits(${label}-min)] } {
1508            set amin $_limits(${label}-min)
1509        }
1510        set amax ""
1511        if { [info exists _limits(${label}-max)] } {
1512            set amax $_limits(${label}-max)
1513        }
1514        set axis $_label2axis($label)
1515        $g axis configure $axis \
1516            -hide no -checklimits no \
1517            -command [itcl::code $this GetFormattedValue $axis] \
1518            -min $amin -max $amax -logscale $logscale
1519        $g axis bind $axis <Enter> \
1520            [itcl::code $this Axis hilite $axis on]
1521        $g axis bind $axis <Leave> \
1522            [itcl::code $this Axis hilite $axis off]
1523        $g axis bind $axis <ButtonPress-1> \
1524            [itcl::code $this Axis click $axis %x %y]
1525        $g axis bind $axis <B1-Motion> \
1526            [itcl::code $this Axis drag $axis %x %y]
1527        $g axis bind $axis <ButtonRelease-1> \
1528            [itcl::code $this Axis release $axis %x %y]
1529        $g axis bind $axis <KeyPress> \
1530            [list ::Rappture::Tooltip::tooltip cancel]
1531    }
1532   
1533    foreach dataobj $dlist {
1534        SetElements $dataobj
1535    }
1536    ResetLegend
1537}
1538
1539#
1540# SetElements --
1541#
1542#       This procedure loads each data objects specified into the
1543#       graph.  The data object may already be loaded (from the "add"
1544#       method which gets called first).   The graph elements that
1545#       are created, are hidden.  This allows the graph to account
1546#       for all datasets, even those not currently being displayed.
1547#       
1548itcl::body Rappture::XyResult::SetElements { dataobj {settings ""} } {
1549    set g $itk_component(plot)
1550
1551    array set attrs [$dataobj hints style]
1552    array set attrs $settings
1553    set type [$dataobj hints type]
1554    if { $type == "" } {
1555        set type "line"
1556    }
1557
1558    # Now fix attributes to a more usable form for the graph.
1559
1560    # Convert -linestyle to BLT -dashes
1561    if { ![info exists attrs(-linestyle)] } {
1562        set dashes {}
1563    } else {
1564        switch -- $attrs(-linestyle) {
1565            dashed  { set dashes {4 4} }
1566            dotted  { set dashes {2 4} }
1567            default { set dashes {}    }
1568        }
1569    }
1570    if { ![info exists attrs(-barwidth)] } {
1571        set barwidth 1.0
1572    } else {
1573        set barwidth $attrs(-barwidth)
1574    }
1575    if { ![info exists attrs(-width)] } {
1576        set linewidth 1
1577    } else {
1578        set linewidth $attrs(-width)
1579    }
1580    set _dataobj2raise($dataobj) [info exists attrs(-raise)]
1581    if { ![info exists attrs(-simulation)] } {
1582        set sim 0
1583    } else {
1584        set sim $attrs(-simulation)
1585    }
1586    set _dataobj2sim($dataobj) $sim
1587
1588    foreach {mapx mapy} [GetAxes $dataobj] break
1589    set label [$dataobj hints label]
1590
1591    foreach cname [$dataobj components] {
1592        set tag $dataobj-$cname
1593        set xv [$dataobj mesh $cname]
1594        set yv [$dataobj values $cname]
1595        set xev [$dataobj xErrorValues $cname]
1596        set yev [$dataobj yErrorValues $cname]
1597        if {([$xv length] <= 1) || ($linewidth == 0)} {
1598            set sym square
1599            set pixels 2
1600        } else {
1601            set sym ""
1602            set pixels 6
1603        }
1604        if { ![info exists _comp2elem($tag)] } {
1605            set elem "$type[incr _nextElement]"
1606            set _elem2comp($elem) $tag
1607            set _comp2elem($tag) $elem
1608            set found($_comp2elem($tag)) 1
1609            lappend label2elem($label) $elem
1610            switch -- $type {
1611                "line" {
1612                    $g line create $elem \
1613                        -x $xv -y $yv \
1614                        -symbol $sym \
1615                        -pixels $pixels \
1616                        -linewidth $linewidth \
1617                        -label $label \
1618                        -dashes $dashes \
1619                        -mapx $mapx \
1620                        -mapy $mapy \
1621                        -hide yes \
1622                        -xerror $xev -yerror $yev
1623                }
1624                "scatter" {               
1625                    $g line create $elem \
1626                        -x $xv -y $yv \
1627                        -symbol square \
1628                        -pixels 2 \
1629                        -linewidth 0 \
1630                        -label $label \
1631                        -dashes $dashes \
1632                        -mapx $mapx \
1633                        -mapy $mapy \
1634                        -hide yes \
1635                        -xerror $xev -yerror $yev
1636                }
1637                "bar" {
1638                    $g bar create $elem \
1639                        -x $xv -y $yv \
1640                        -barwidth $barwidth \
1641                        -label $label \
1642                        -mapx $mapx \
1643                        -mapy $mapy \
1644                        -hide yes \
1645                        -xerror $xev -yerror $yev
1646                }
1647            }
1648        } else {
1649            $g element configure $_comp2elem($tag) -mapx $mapx -mapy $mapy
1650        }
1651    }
1652}
Note: See TracBrowser for help on using the repository browser.