source: trunk/gui/scripts/xyresult.tcl @ 6126

Last change on this file since 6126 was 6126, checked in by ldelgass, 8 years ago

merge r6118,r6120 from 1.5 branch

File size: 58.7 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                    set dlist ""
425                    set g $itk_component(plot)
426
427                    # Build up a list of download-able dataobjs. Add
428                    # objects that are currently viewable (selected by the
429                    # simulation selector) and visible in the graph
430                    # (selected by the legend).
431                    foreach elem $_viewable {
432                        foreach {dataobj cname} \
433                            [split $_elem2comp($elem) -] break
434
435                        # Build a lookup table of graph elements associated
436                        # with each label. There will be more than one
437                        # element for each entry if there is more than one
438                        # simulation currently being downloaded.
439
440                        set label [$dataobj hints label]
441                        lappend label2elem($label) $elem
442
443                        # Ignore hidden graph elements.
444                        if { [$g element cget $elem -hide] } {
445                            continue
446                        }
447                        lappend dlist $dataobj
448
449                    }
450
451                    # Generate the comma-separated value data for these
452                    # objects.
453                    set csvdata ""
454                    foreach dataobj $dlist {
455                        append csvdata "[string repeat - 60]\n"
456                        append csvdata " [$dataobj hints label]\n"
457                        if { [info exists _dataobj2desc($dataobj)] } {
458                            set indent "for:"
459                            foreach line [split $_dataobj2desc($dataobj) \n] {
460                                append csvdata " $indent $line\n"
461                                set indent "    "
462                            }
463                        }
464                        append csvdata "[string repeat - 60]\n"
465                        set sim $_dataobj2sim($dataobj)
466                        set xlabel [$dataobj hints xlabel]
467                        set ylabel [$dataobj hints ylabel]
468                        set label [$dataobj hints label]
469                        if { [llength $label2elem($label)] > 1 } {
470                            set xlabel [format "%s (\#%d)" $xlabel $sim]
471                            set ylabel [format "%s (\#%d)" $ylabel $sim]
472                        }
473                        append csvdata "$xlabel, $ylabel\n"
474                        set first 1
475                        foreach comp [$dataobj components] {
476                            if {!$first} {
477                                # blank line between components
478                                append csvdata "\n"
479                            }
480                            set xv [$dataobj mesh $comp]
481                            set yv [$dataobj values $comp]
482                            foreach x [$xv range 0 end] y [$yv range 0 end] {
483                                append csvdata [format "%20.15g, %20.15g\n" $x $y]
484                            }
485                            set first 0
486                        }
487                        append csvdata "\n"
488                    }
489                    return [list .txt $csvdata]
490                }
491                image {
492                    set popup .xyprintdownload
493                    if { ![winfo exists $popup] } {
494                        # Create a popup for the print dialog
495                        Rappture::Balloon $popup -title "Save as image..."
496                        set inner [$popup component inner]
497                        # Create the print dialog widget and add it to the
498                        # balloon popup.
499                        Rappture::XyPrint $inner.print
500                        $popup configure \
501                            -deactivatecommand [list $inner.print reset]
502                        blt::table $inner 0,0 $inner.print -fill both
503                    }
504                    update
505                    # Activate the popup and call for the output.
506                    foreach { widget toolName plotName } $args break
507                    $popup activate $widget left
508                    set inner [$popup component inner]
509                    set output [$inner.print print $itk_component(plot) \
510                                    $toolName $plotName]
511                    $popup deactivate
512                    return $output
513                }
514            }
515        }
516        default {
517            error "bad option \"$option\": should be coming, controls, now"
518        }
519    }
520}
521
522itcl::body Rappture::XyResult::BuildMarkers { dataobj elem } {
523    set g $itk_component(plot)
524
525    set ymin -Inf
526    set ymax Inf
527    set xmax Inf
528    set xmin -Inf
529    foreach m [$dataobj xmarkers] {
530        foreach {at label style} $m break
531        set id [$g marker create line -coords [list $at $ymin $at $ymax]]
532        $g marker bind $id <Enter> \
533            [itcl::code $this EnterMarker $g x-$label $at $ymin $at]
534        $g marker bind $id <Leave> \
535            [itcl::code $this LeaveMarker $g x-$label]
536        set options [GetLineMarkerOptions $style]
537        $g marker configure $id -element $elem
538        if { $options != "" } {
539            eval $g marker configure $id $options
540        }
541        if { $label != "" } {
542            set id [$g marker create text -anchor nw \
543                        -text $label -coords [list $at $ymax]]
544            $g marker configure $id -element $elem
545            set options [GetTextMarkerOptions $style]
546            if { $options != "" } {
547                eval $g marker configure $id $options
548            }
549        }
550    }
551    foreach m [$dataobj ymarkers] {
552        foreach {at label style} $m break
553        set id [$g marker create line -coords [list $xmin $at $xmax $at]]
554        $g marker configure $id -element $elem
555        $g marker bind $id <Enter> \
556            [itcl::code $this EnterMarker $g $label $at $xmin $at]
557        $g marker bind $id <Leave> \
558            [itcl::code $this LeaveMarker $g $label]
559        set options [GetLineMarkerOptions $style]
560        if { $options != "" } {
561            eval $g marker configure $id $options
562        }
563        if { $label != "" } {
564            set id [$g marker create text -anchor se \
565                        -text $label -coords [list $xmax $at]]
566            $g marker configure $id -element $elem
567            set options [GetTextMarkerOptions $style]
568            if { $options != "" } {
569                eval $g marker configure $id $options
570            }
571        }
572    }
573}
574
575# ----------------------------------------------------------------------
576# USAGE: Rebuild
577#
578#       Called automatically whenever something changes that affects the
579# data in the widget.  Clears any existing data and rebuilds the
580# widget to display new data.
581# ----------------------------------------------------------------------
582itcl::body Rappture::XyResult::Rebuild {} {
583    ResetLegend
584    # Fix raise/lower elements
585}
586
587# ----------------------------------------------------------------------
588# USAGE: ResetLimits
589#
590# Used internally to apply automatic limits to the axes for the
591# current plot.
592# ----------------------------------------------------------------------
593itcl::body Rappture::XyResult::ResetLimits {} {
594    set g $itk_component(plot)
595
596    foreach axis [$g axis names] {
597        $g axis configure $axis -min "" -max ""
598    }
599}
600
601# ----------------------------------------------------------------------
602# USAGE: ResetLegend
603#
604# Used internally to apply automatic limits to the axes for the
605# current plot.
606# ----------------------------------------------------------------------
607itcl::body Rappture::XyResult::ResetLegend {} {
608    update idletasks
609
610    set g $itk_component(plot)
611    # Fix duplicate labels by appending the simulation number
612    # Collect the labels from all the viewable elements.
613    set above {}
614    set below {}
615    foreach elem $_viewable {
616        foreach {dataobj cname} [split $_elem2comp($elem) -] break
617        set label [$dataobj hints label]
618        lappend label2elem($label) $elem
619        if { $_dataobj2raise($dataobj) } {
620            lappend $g element raise $elem
621        }
622    }
623    # Then relabel elements with the same label, using the simulation number.
624    foreach label [array names label2elem] {
625        foreach elem $label2elem($label) {
626            if { [llength $label2elem($label)] == 1 } {
627                $g element configure $elem -label $label
628                continue
629            }
630            foreach {dataobj cname} [split $_elem2comp($elem) -] break
631            set sim $_dataobj2sim($dataobj)
632            set elabel [format "%s (\#%d)" $label $sim]
633            $g element configure $elem -label $elabel
634        }
635    }
636    $itk_component(legend) reset $_viewable
637}
638
639
640# ----------------------------------------------------------------------
641# USAGE: Zoom reset
642#
643# Called automatically when the user clicks on one of the zoom
644# controls for this widget.  Changes the zoom for the current view.
645# ----------------------------------------------------------------------
646itcl::body Rappture::XyResult::Zoom {option args} {
647    switch -- $option {
648        reset {
649            ResetLimits
650            Rappture::Logger::log curve zoom -reset
651        }
652    }
653}
654
655# ----------------------------------------------------------------------
656# USAGE: Hilite <state> <x> <y>
657#
658# Called automatically when the user brushes one of the elements
659# on the plot.  Causes the element to highlight and a tooltip to
660# pop up with element info.
661# ----------------------------------------------------------------------
662itcl::body Rappture::XyResult::Hilite {state x y} {
663    set g $itk_component(plot)
664    set elem ""
665
666    # Peek inside of Blt_ZoomStack package to see if we're currently in the
667    # middle of a zoom selection.
668    if {[info exists ::zoomInfo($g,corner)] && $::zoomInfo($g,corner) == "B" } {
669        return;
670    }
671    set tip ""
672    if {$state == "at"} {
673        if {[$g element closest $x $y info -interpolate yes]} {
674            # for dealing with xy line plots
675            set elem $info(name)
676
677            set mapx [$g element cget $elem -mapx]
678            set mapy [$g element cget $elem -mapy]
679
680            # search again for an exact point -- this time don't interpolate
681            set tip ""
682            array unset info
683            if {[$g element closest $x $y info -interpolate no]
684                  && $info(name) == $elem} {
685
686                set x [$g axis transform $mapx $info(x)]
687                set y [$g axis transform $mapy $info(y)]
688                if {[info exists _elem2comp($elem)]} {
689                    foreach {dataobj cname} [split $_elem2comp($elem) -] break
690                    set yunits [$dataobj hints yunits]
691                    set xunits [$dataobj hints xunits]
692                } else {
693                    set xunits ""
694                    set yunits ""
695                }
696                set tip [$g element cget $elem -label]
697                set yval [GetFormattedValue y $g $info(y)]
698                append tip "\n$yval$yunits"
699                set xval [GetFormattedValue x $g $info(x)]
700                append tip " @ $xval$xunits"
701                set tip [string trim $tip]
702            }
703            set state 1
704        } elseif {[$g element closest $x $y info -interpolate no]} {
705            # for dealing with xy scatter plot
706            set elem $info(name)
707
708            set mapx [$g element cget $elem -mapx]
709            set mapy [$g element cget $elem -mapy]
710
711            set tip ""
712            set x [$g axis transform $mapx $info(x)]
713            set y [$g axis transform $mapy $info(y)]
714
715            if {[info exists _elem2comp($elem)]} {
716                foreach {dataobj cname} [split $_elem2comp($elem) -] break
717                set yunits [$dataobj hints yunits]
718                set xunits [$dataobj hints xunits]
719            } else {
720                set xunits ""
721                set yunits ""
722            }
723            set tip [$g element cget $elem -label]
724            set yval [GetFormattedValue y $g $info(y)]
725            append tip "\n$yval$yunits"
726            set xval [GetFormattedValue x $g $info(x)]
727            append tip " @ $xval$xunits"
728            set tip [string trim $tip]
729            set state 1
730        } else {
731            set state 0
732        }
733    }
734
735    if {$state} {
736        #
737        # Highlight ON:
738        # - activate trace
739        # - multiple axes? dim other axes
740        # - pop up tooltip about data
741        #
742        if { [$g element exists $_hilite(elem)] && $_hilite(elem) != $elem } {
743            $g element deactivate $_hilite(elem)
744            $g crosshairs configure -hide yes
745            Rappture::Tooltip::tooltip cancel
746        }
747        $g element activate $elem
748        set _hilite(elem) $elem
749
750        set mapx [$g element cget $elem -mapx]
751        set mapy [$g element cget $elem -mapy]
752        if {[info exists _elem2comp($elem)]} {
753            foreach {dataobj cname} [split $_elem2comp($elem) -] break
754            foreach {mapx mapy} [GetAxes $dataobj] break
755        }
756        set allx [$g x2axis use]
757        if {[llength $allx] > 0} {
758            lappend allx x  ;           # fix main x-axis too
759            foreach axis $allx {
760                if {$axis == $mapx} {
761                    $g axis configure $axis -color $itk_option(-foreground) \
762                        -titlecolor $itk_option(-foreground)
763                } else {
764                    $g axis configure $axis -color $itk_option(-dimcolor) \
765                        -titlecolor $itk_option(-dimcolor)
766                }
767            }
768        }
769        set ally [$g y2axis use]
770        if {[llength $ally] > 0} {
771            lappend ally y  ;           # fix main y-axis too
772            foreach axis $ally {
773                if {$axis == $mapy} {
774                    $g axis configure $axis -color $itk_option(-foreground) \
775                        -titlecolor $itk_option(-foreground)
776                } else {
777                    $g axis configure $axis -color $itk_option(-dimcolor) \
778                        -titlecolor $itk_option(-dimcolor)
779                }
780            }
781        }
782        if {"" != $tip} {
783            $g crosshairs configure -hide no -position @$x,$y
784
785            if {$x > 0.5*[winfo width $g]} {
786                if {$x < 4} {
787                    set tipx "-0"
788                } else {
789                    set tipx "-[expr {$x-20}]"  ;# move tooltip to the left
790                }
791            } else {
792                if {$x < -4} {
793                    set tipx "+0"
794                } else {
795                    set tipx "+[expr {$x+20}]"  ;# move tooltip to the right
796                }
797            }
798            if {$y > 0.5*[winfo height $g]} {
799                if {$y < 4} {
800                    set tipy "-0"
801                } else {
802                    set tipy "-[expr {$y-20}]"  ;# move tooltip to the top
803                }
804            } else {
805                if {$y < -4} {
806                    set tipy "+0"
807                } else {
808                    set tipy "+[expr {$y+20}]"  ;# move tooltip to the bottom
809                }
810            }
811            Rappture::Tooltip::text $g $tip
812            Rappture::Tooltip::tooltip show $g $tipx,$tipy
813            Rappture::Logger::log tooltip -for "curve probe -- [string map [list \n " // "] $tip]"
814        }
815    } else {
816        #
817        # Highlight OFF:
818        # - deactivate (color back to normal)
819        # - put all axes back to normal color
820        # - take down tooltip
821        #
822        if { [$g element exists $_hilite(elem)] } {
823            $g element deactivate $_hilite(elem)
824        }
825        set allx [$g x2axis use]
826        if {[llength $allx] > 0} {
827            lappend allx x  ;# fix main x-axis too
828            foreach axis $allx {
829                $g axis configure $axis -color $itk_option(-foreground) \
830                    -titlecolor $itk_option(-foreground)
831            }
832        }
833
834        set ally [$g y2axis use]
835        if {[llength $ally] > 0} {
836            lappend ally y  ;# fix main y-axis too
837            foreach axis $ally {
838                $g axis configure $axis -color $itk_option(-foreground) \
839                    -titlecolor $itk_option(-foreground)
840            }
841        }
842
843        $g crosshairs configure -hide yes
844
845        # only cancel in plotting area or we'll mess up axes
846        if {[$g inside $x $y]} {
847            Rappture::Tooltip::tooltip cancel
848        }
849
850        # There is no currently highlighted element
851        set _hilite(elem) ""
852    }
853}
854
855# ----------------------------------------------------------------------
856# USAGE: Axis hilite <axis> <state>
857#
858# USAGE: Axis click <axis> <x> <y>
859# USAGE: Axis drag <axis> <x> <y>
860# USAGE: Axis release <axis> <x> <y>
861#
862# Used internally to handle editing of the x/y axes.  The hilite
863# operation causes the axis to light up.  The edit operation pops
864# up a panel with editing options.  The changed operation applies
865# changes from the panel.
866# ----------------------------------------------------------------------
867itcl::body Rappture::XyResult::Axis {option args} {
868    switch -- $option {
869        hilite {
870            if {[llength $args] != 2} {
871                error "wrong # args: should be \"Axis hilite axis state\""
872            }
873            set g $itk_component(plot)
874            set axis [lindex $args 0]
875            set state [lindex $args 1]
876
877            if {$state} {
878                $g axis configure $axis \
879                    -color $itk_option(-activecolor) \
880                    -titlecolor $itk_option(-activecolor)
881
882                set x [expr {[winfo pointerx $g]+4}]
883                set y [expr {[winfo pointery $g]+4}]
884                Rappture::Tooltip::tooltip pending $g-$axis @$x,$y
885            } else {
886                $g axis configure $axis \
887                    -color $itk_option(-foreground) \
888                    -titlecolor $itk_option(-foreground)
889                Rappture::Tooltip::tooltip cancel
890            }
891        }
892        click {
893            if {[llength $args] != 3} {
894                error "wrong # args: should be \"Axis click axis x y\""
895            }
896            set axis [lindex $args 0]
897            set x [lindex $args 1]
898            set y [lindex $args 2]
899            set g $itk_component(plot)
900
901            set _axis(moved) 0
902            set _axis(click-x) $x
903            set _axis(click-y) $y
904            foreach {min max} [$g axis limits $axis] break
905            set _axis(min0) $min
906            set _axis(max0) $max
907            Rappture::Tooltip::tooltip cancel
908        }
909        drag {
910            if {[llength $args] != 3} {
911                error "wrong # args: should be \"Axis drag axis x y\""
912            }
913            if {![info exists _axis(moved)]} {
914                return  ;# must have skipped click event -- ignore
915            }
916            set axis [lindex $args 0]
917            set x [lindex $args 1]
918            set y [lindex $args 2]
919            set g $itk_component(plot)
920
921            if {[info exists _axis(click-x)] && [info exists _axis(click-y)]} {
922                foreach {x0 y0 pw ph} [$g extents plotarea] break
923                switch -glob $axis {
924                  x* {
925                    set pix $x
926                    set pix0 $_axis(click-x)
927                    set pixmin $x0
928                    set pixmax [expr {$x0+$pw}]
929                  }
930                  y* {
931                    set pix $y
932                    set pix0 $_axis(click-y)
933                    set pixmin [expr {$y0+$ph}]
934                    set pixmax $y0
935                  }
936                }
937                set log [$g axis cget $axis -logscale]
938                set min $_axis(min0)
939                set max $_axis(max0)
940                set dpix [expr {abs($pix-$pix0)}]
941                set v0 [$g axis invtransform $axis $pixmin]
942                set v1 [$g axis invtransform $axis [expr {$pixmin+$dpix}]]
943                if {$log} {
944                    set v0 [expr {log10($v0)}]
945                    set v1 [expr {log10($v1)}]
946                    set min [expr {log10($min)}]
947                    set max [expr {log10($max)}]
948                }
949
950                if {$pix > $pix0} {
951                    set delta [expr {$v1-$v0}]
952                } else {
953                    set delta [expr {$v0-$v1}]
954                }
955                set min [expr {$min-$delta}]
956                set max [expr {$max-$delta}]
957                if {$log} {
958                    set min [expr {pow(10.0,$min)}]
959                    set max [expr {pow(10.0,$max)}]
960                }
961                $g axis configure $axis -min $min -max $max
962
963                # move axis, don't edit on release
964                set _axis(move) 1
965            }
966        }
967        release {
968            if {[llength $args] != 3} {
969                error "wrong # args: should be \"Axis release axis x y\""
970            }
971            if {![info exists _axis(moved)]} {
972                return  ;# must have skipped click event -- ignore
973            }
974            set axis [lindex $args 0]
975            set x [lindex $args 1]
976            set y [lindex $args 2]
977
978            if {!$_axis(moved)} {
979                # small movement? then treat as click -- pop up axis editor
980                set dx [expr {abs($x-$_axis(click-x))}]
981                set dy [expr {abs($y-$_axis(click-y))}]
982                if {$dx < 2 && $dy < 2} {
983                    ShowAxisPopup $axis
984                    return
985                }
986            }
987            # one last movement
988            Axis drag $axis $x $y
989
990            # log this change
991            Rappture::Logger::log curve axis $axis \
992                -drag [$itk_component(plot) axis limits $axis]
993
994            catch {unset _axis}
995        }
996        default {
997            error "bad option \"$option\": should be hilite"
998        }
999    }
1000}
1001
1002
1003# ----------------------------------------------------------------------
1004# USAGE: GetLineMarkerOptions <style>
1005#
1006# Used internally to create a list of configuration options specific to the
1007# axis line marker.  The input is a list of name value pairs.  Options that
1008# are not recognized are ignored.
1009# ----------------------------------------------------------------------
1010itcl::body Rappture::XyResult::GetLineMarkerOptions {style} {
1011    array set lineOptions {
1012        "-color"  "-outline"
1013        "-dashes" "-dashes"
1014        "-linecolor" "-outline"
1015        "-linewidth" "-linewidth"
1016    }
1017    set options {}
1018    foreach {name value} $style {
1019        if { [info exists lineOptions($name)] } {
1020            lappend options $lineOptions($name) $value
1021        }
1022    }
1023    return $options
1024}
1025
1026# ----------------------------------------------------------------------
1027# USAGE: GetTextMarkerOptions <style>
1028#
1029# Used internally to create a list of configuration options specific to the
1030# axis text marker.  The input is a list of name value pairs.  Options that
1031# are not recognized are ignored.
1032# ----------------------------------------------------------------------
1033itcl::body Rappture::XyResult::GetTextMarkerOptions {style} {
1034    array set textOptions {
1035        "-color"        "-outline"
1036        "-textcolor"    "-outline"
1037        "-font"         "-font"
1038        "-xoffset"      "-xoffset"
1039        "-yoffset"      "-yoffset"
1040        "-anchor"       "-anchor"
1041        "-rotate"       "-rotate"
1042    }
1043    set options {}
1044    foreach {name value} $style {
1045        if { [info exists textOptions($name)] } {
1046            lappend options $textOptions($name) $value
1047        }
1048    }
1049    return $options
1050}
1051
1052# ----------------------------------------------------------------------
1053# USAGE: GetAxes <dataobj>
1054#
1055# Used internally to figure out the axes used to plot the given
1056# <dataobj>.  Returns a list of the form {x y}, where x is the
1057# x-axis name (x, x2, x3, etc.), and y is the y-axis name.
1058# ----------------------------------------------------------------------
1059itcl::body Rappture::XyResult::GetAxes {dataobj} {
1060    set xlabel [$dataobj hints xlabel]
1061    if {[info exists _label2axis($xlabel)]} {
1062        set mapx $_label2axis($xlabel)
1063    } else {
1064        set mapx "x"
1065    }
1066
1067    set ylabel [$dataobj hints ylabel]
1068    if {[info exists _label2axis($ylabel)]} {
1069        set mapy $_label2axis($ylabel)
1070    } else {
1071        set mapy "y"
1072    }
1073    return [list $mapx $mapy]
1074}
1075
1076# ----------------------------------------------------------------------
1077# CONFIGURATION OPTION: -gridcolor
1078# ----------------------------------------------------------------------
1079itcl::configbody Rappture::XyResult::gridcolor {
1080    if {"" == $itk_option(-gridcolor)} {
1081        $itk_component(plot) grid off
1082    } else {
1083        $itk_component(plot) grid configure -color $itk_option(-gridcolor)
1084        $itk_component(plot) grid on
1085    }
1086}
1087
1088# ----------------------------------------------------------------------
1089# CONFIGURATION OPTION: -autocolors
1090# ----------------------------------------------------------------------
1091itcl::configbody Rappture::XyResult::autocolors {
1092    foreach c $itk_option(-autocolors) {
1093        if {[catch {winfo rgb $itk_component(hull) $c}]} {
1094            error "bad color \"$c\""
1095        }
1096    }
1097        incr _nextColorIndex
1098    if {$_nextColorIndex >= [llength $itk_option(-autocolors)]} {
1099        set _nextColorIndex 0
1100    }
1101}
1102
1103itcl::body Rappture::XyResult::EnterMarker { g name x y text } {
1104    LeaveMarker $g $name
1105    set id [$g marker create text \
1106                -coords [list $x $y] \
1107                -yoffset -1 \
1108                -anchor s \
1109                -text $text]
1110    set _markers($name) $id
1111}
1112
1113itcl::body Rappture::XyResult::LeaveMarker { g name } {
1114    if { [info exists _markers($name)] } {
1115        set id $_markers($name)
1116        $g marker delete $id
1117        unset _markers($name)
1118    }
1119}
1120
1121#
1122# SetAxis --
1123#
1124#       Configures the graph axis with the designated setting using
1125#       the currently stored value.  User-configurable axis settings
1126#       are stored in the _axisPopup variable or in the widgets. This
1127#       routine syncs the graph with that setting.
1128#
1129itcl::body Rappture::XyResult::SetAxis { setting } {
1130    set g $itk_component(plot)
1131    set axis $_axisPopup(axis)
1132    switch -- $setting {
1133        "logscale" {
1134            set bool $_axisPopup(logscale)
1135            $g axis configure $axis -logscale $bool
1136        }
1137        "loose" {
1138            set bool $_axisPopup(loose)
1139            $g axis configure $axis -loose $bool
1140        }
1141        "range" {
1142            set auto $_axisPopup(auto)
1143            set _axisPopup($axis-auto) $auto
1144            if { $auto } {
1145                # Set the axis range automatically
1146                $g axis configure $axis -min "" -max ""
1147            } else {
1148                # Set the axis range from the entry values.
1149                set label $_axisPopup(label)
1150                set min $_axisPopup(${label}-min)
1151                set max $_axisPopup(${label}-max)
1152                $g axis configure $axis -min $min -max $max
1153            }
1154            SetAxisRangeState $axis
1155        }
1156        "format" {
1157            set inner [$itk_component(hull).axes component inner]
1158            set format [$inner.format translate [$inner.format value]]
1159            set _axisPopup($axis-format) $format
1160
1161            # Force the graph to reformat the ticks
1162            set min [$itk_component(plot) axis cget $axis -min]
1163            $g axis configure $axis -min $min
1164        }
1165        "label" {
1166            set label $_axisPopup(label)
1167            $g axis configure $axis -title $label
1168        }
1169        "min" {
1170            set min $_axisPopup(min)
1171            if { [catch { $g axis configure $axis -min $min } msg] != 0 } {
1172                set inner [$itk_component(hull).axes component inner]
1173                Rappture::Tooltip::cue $inner.max $msg
1174                bell
1175                return
1176            }
1177            set label $_axisPopup(label)
1178            set _axisPopup(${label}-min) $min
1179        }
1180        "max" {
1181            set max $_axisPopup(max)
1182            if { [catch { $g axis configure $axis -max $max } msg] != 0 } {
1183                set inner [$itk_component(hull).axes component inner]
1184                Rappture::Tooltip::cue $inner.max $msg
1185                bell
1186                return
1187            }
1188            set label $_axisPopup(label)
1189            set _axisPopup(${label}-max) $max
1190        }
1191    }
1192}
1193
1194#
1195# SetAxisRangeState --
1196#
1197#       Sets the state of widgets controlling the axis range based
1198#       upon whether the automatic or manual setting.  If the
1199#       axis is configure to be automatic, the manual setting widgets
1200#       are disabled.  And vesa-versa the automatic setting widgets
1201#       are dsiabled if the axis is manual.
1202#
1203itcl::body Rappture::XyResult::SetAxisRangeState { axis } {
1204    set inner [$itk_component(hull).axes component inner]
1205    set g $itk_component(plot)
1206
1207    if { $_axisPopup(auto) } {
1208        foreach {min max} [$g axis limits $axis] break
1209        $inner.minl configure -state disabled
1210        $inner.min configure -state disabled
1211        $inner.maxl configure -state disabled
1212        $inner.max configure -state disabled
1213        $inner.loose configure -state normal
1214        $inner.tight configure -state normal
1215    } else {
1216        foreach {min max} [$g axis limits $axis] break
1217        $inner.minl configure -state normal
1218        $inner.min configure -state normal
1219        set _axisPopup(min) [$g axis cget $axis -min]
1220        $inner.maxl configure -state normal
1221        $inner.max configure -state normal
1222        set _axisPopup(max) [$g axis cget $axis -max]
1223        $inner.loose configure -state disabled
1224        $inner.tight configure -state disabled
1225    }
1226}
1227
1228#
1229# BuildAxisPopup --
1230#
1231#       Creates the popup balloon dialog for axes. This routine is
1232#       called only once the first time the user clicks to bring up
1233#       an axis dialog.  It is reused for all other axes.
1234#
1235itcl::body Rappture::XyResult::BuildAxisPopup { popup } {
1236    Rappture::Balloon $popup -title "Axis Options"
1237    set inner [$itk_component(hull).axes component inner]
1238
1239    label $inner.labell -text "Label:"
1240    entry $inner.label \
1241        -width 15 -highlightbackground $itk_option(-background) \
1242        -textvariable [itcl::scope _axisPopup(label)]
1243
1244    bind $inner.label <Return>   [itcl::code $this SetAxis label]
1245    bind $inner.label <KP_Enter> [itcl::code $this SetAxis label]
1246    bind $inner.label <FocusOut> [itcl::code $this SetAxis label]
1247
1248    label $inner.formatl -text "Format:"
1249    Rappture::Combobox $inner.format -width 15 -editable no
1250    $inner.format choices insert end \
1251        "%.6g"  "Auto"         \
1252        "%.0f"  "X"          \
1253        "%.1f"  "X.X"          \
1254        "%.2f"  "X.XX"         \
1255        "%.3f"  "X.XXX"        \
1256        "%.6f"  "X.XXXXXX"     \
1257        "%.1e"  "X.Xe+XX"      \
1258        "%.2e"  "X.XXe+XX"     \
1259        "%.3e"  "X.XXXe+XX"    \
1260        "%.6e"  "X.XXXXXXe+XX"
1261
1262    bind $inner.format <<Value>> [itcl::code $this SetAxis format]
1263
1264    label $inner.rangel -text "Axis Range:"
1265    radiobutton $inner.auto -text "Automatic" \
1266        -variable [itcl::scope _axisPopup(auto)] -value 1 \
1267        -command [itcl::code $this SetAxis range]
1268    radiobutton $inner.manual -text "Manual" \
1269        -variable [itcl::scope _axisPopup(auto)] -value 0 \
1270        -command [itcl::code $this SetAxis range]
1271
1272    radiobutton $inner.loose -text "loose" \
1273        -variable [itcl::scope _axisPopup(loose)] -value 1 \
1274        -command [itcl::code $this SetAxis loose]
1275    radiobutton $inner.tight -text "tight" \
1276        -variable [itcl::scope _axisPopup(loose)] -value 0 \
1277        -command [itcl::code $this SetAxis loose]
1278
1279    label $inner.minl -text "min"
1280    entry $inner.min \
1281        -width 15 -highlightbackground $itk_option(-background) \
1282        -textvariable [itcl::scope _axisPopup(min)]
1283    bind $inner.min <Return> [itcl::code $this SetAxis min]
1284    bind $inner.min <KP_Enter> [itcl::code $this SetAxis min]
1285    bind $inner.min <FocusOut> [itcl::code $this SetAxis min]
1286
1287    label $inner.maxl -text "max"
1288    entry $inner.max \
1289        -width 15 -highlightbackground $itk_option(-background) \
1290        -textvariable [itcl::scope _axisPopup(max)]
1291    bind $inner.max <Return> [itcl::code $this SetAxis max]
1292    bind $inner.max <KP_Enter> [itcl::code $this SetAxis max]
1293    bind $inner.max <FocusOut> [itcl::code $this SetAxis max]
1294
1295
1296    label $inner.scalel -text "Scale:"
1297    radiobutton $inner.linear -text "linear" \
1298        -variable [itcl::scope _axisPopup(logscale)] -value 0 \
1299        -command [itcl::code $this SetAxis logscale]
1300    radiobutton $inner.log -text "logarithmic" \
1301        -variable [itcl::scope _axisPopup(logscale)] -value 1 \
1302        -command [itcl::code $this SetAxis logscale]
1303
1304    blt::table $inner \
1305        0,0 $inner.labell -anchor w \
1306        0,1 $inner.label -anchor w -fill x  -cspan 3 \
1307        1,0 $inner.formatl -anchor w \
1308        1,1 $inner.format -anchor w -fill x  -cspan 3 \
1309        2,0 $inner.scalel -anchor w \
1310        2,2 $inner.linear -anchor w \
1311        2,3 $inner.log -anchor w \
1312        3,0 $inner.rangel -anchor w \
1313        4,0 $inner.manual -anchor w -padx 4 \
1314        4,2 $inner.minl -anchor e \
1315        4,3 $inner.min -anchor w \
1316        5,2 $inner.maxl -anchor e \
1317        5,3 $inner.max -anchor w \
1318        6,0 $inner.auto -anchor w -padx 4 \
1319        6,2 $inner.tight -anchor w \
1320        6,3 $inner.loose -anchor w \
1321
1322
1323    blt::table configure $inner r2 -pady 4
1324    blt::table configure $inner c1 -width 20
1325    update
1326}
1327
1328#
1329# ShowAxisPopup --
1330#
1331#       Displays the axis dialog for an axis.  It initializes the
1332#       _axisInfo variables for that axis if necessary.
1333#
1334itcl::body Rappture::XyResult::ShowAxisPopup { axis } {
1335    set g $itk_component(plot)
1336    set popup $itk_component(hull).axes
1337
1338    if { ![winfo exists $popup] } {
1339        BuildAxisPopup $popup
1340    }
1341    set _axisPopup(axis)     $axis
1342    set _axisPopup(label)    [$g axis cget $axis -title]
1343    set _axisPopup(logscale) [$g axis cget $axis -logscale]
1344    set _axisPopup(loose)    [$g axis cget $axis -loose]
1345    if { ![info exists _axisPopup($axis-format)] } {
1346        set inner [$itk_component(hull).axes component inner]
1347        set _axisPopup($axis-format) "%.6g"
1348        set fmts [$inner.format choices get -value]
1349        set i [lsearch -exact $fmts $_axisPopup($axis-format)]
1350        if {$i < 0} { set i 0 }  ;# use Auto choice
1351        $inner.format value [$inner.format choices get -label $i]
1352    }
1353    foreach {min max} [$g axis limits $axis] break
1354    if { $_axisPopup(logscale) } {
1355        set type "log"
1356    } else {
1357        set type "lin"
1358    }
1359    set amin ""
1360    set label $_axisPopup(label)
1361    if { [info exists _limits(${label}-min)] } {
1362        set amin $_limits(${label}-min)
1363    }
1364    set amax ""
1365    if { [info exists _limits(${label}-max)] } {
1366        set amax $_limits(${label}-max)
1367    }
1368    set auto 1
1369    if { $amin != "" || $amax != "" } {
1370        set auto 0
1371    }
1372    if { ![info exists _axisPopup($axis-auto)] } {
1373        set _axisPopup($axis-auto) $auto;# Defaults to automatic
1374    }
1375    set _axisPopup(auto)  $_axisPopup($axis-auto)
1376    SetAxisRangeState $axis
1377    if { ![info exists _axisPopup(${label}-min)] } {
1378        if { $amin != "" } {
1379            set _axisPopup(${label}-min) $amin
1380            set _axisPopup(min)   $_axisPopup(${label}-min)
1381            SetAxis min
1382        } else {
1383            set _axisPopup(${label}-min) $min
1384        }
1385    }
1386    if { ![info exists _axisPopup(${label}-max)] } {
1387        if { $amax != "" } {
1388            set _axisPopup(${label}-max) $amax
1389            set _axisPopup(max)   $_axisPopup(${label}-max)
1390            SetAxis max
1391        } else {
1392            set _axisPopup(${label}-max) $max
1393        }
1394    }
1395    set _axisPopup(min)  $_axisPopup(${label}-min)
1396    set _axisPopup(max)  $_axisPopup(${label}-max)
1397    set _axisPopup(axis) $axis
1398
1399    #
1400    # Figure out where the window should pop up.
1401    #
1402    set x [winfo rootx $g]
1403    set y [winfo rooty $g]
1404    set w [winfo width $g]
1405    set h [winfo height $g]
1406    foreach {x0 y0 pw ph} [$g extents plotarea] break
1407    switch -glob -- $axis {
1408        x {
1409            set x [expr {round($x + $x0+0.5*$pw)}]
1410            set y [expr {round($y + $y0+$ph + 0.5*($h-$y0-$ph))}]
1411            set dir "above"
1412        }
1413        x* {
1414            set x [expr {round($x + $x0+0.5*$pw)}]
1415            set dir "below"
1416            set allx [$itk_component(plot) x2axis use]
1417            set max [llength $allx]
1418            set i [lsearch -exact $allx $axis]
1419            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1420        }
1421        y {
1422            set x [expr {round($x + 0.5*$x0)}]
1423            set y [expr {round($y + $y0+0.5*$ph)}]
1424            set dir "right"
1425        }
1426        y* {
1427            set y [expr {round($y + $y0+0.5*$ph)}]
1428            set dir "left"
1429            set ally [$g y2axis use]
1430            set max [llength $ally]
1431            set i [lsearch -exact $ally $axis]
1432            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1433            set x [expr {round($x+$x0+$pw + ($i+0.5)*($w-$x0-$pw)/double($max))}]
1434        }
1435    }
1436    $popup activate @$x,$y $dir
1437}
1438
1439#
1440# GetFormattedValue --
1441#
1442#       Callback routine for the axis format procedure.  It formats the
1443#       axis tick label according to the selected format.  This routine
1444#       is also used to format tooltip values.
1445#
1446itcl::body Rappture::XyResult::GetFormattedValue { axis g value } {
1447    if { [$g axis cget $axis -logscale] ||
1448         ![info exists _axisPopup($axis-format)] } {
1449        set fmt "%.6g"
1450    } else {
1451        set fmt $_axisPopup($axis-format)
1452    }
1453    return [format $fmt $value]
1454}
1455
1456
1457#
1458# BuildGraph --
1459#
1460#       This procedure loads each data objects specified into the
1461#       graph.  The data object may already be loaded (from the "add"
1462#       method which gets called first).   The graph elements that
1463#       are created, are hidden.  This allows the graph to account
1464#       for all datasets, even those not currently being displayed.
1465#
1466itcl::body Rappture::XyResult::BuildGraph { dlist } {
1467    set g $itk_component(plot)
1468
1469    foreach label [array names _label2axis] {
1470        set axis $_label2axis($label)
1471        switch -- $axis {
1472            "x" - "x2" - "y" - "y2" {
1473                $g axis configure $axis -hide yes -checklimits no
1474            }
1475            default {
1476                $g axis delete $axis
1477            }
1478        }
1479    }
1480    array unset _label2axis
1481    array unset _limits
1482
1483    # Scan through all objects and create a list of all axes.
1484    # The first x-axis gets mapped to "x".  The second, to "x2".
1485    # Beyond that, we must create new axes "x3", "x4", etc.
1486    # We do the same for y.
1487
1488    set anum(x) 0
1489    set anum(y) 0
1490    foreach dataobj $dlist {
1491        foreach axis {x y} {
1492            set label [$dataobj hints ${axis}label]
1493            if { $label == "" } {
1494                continue
1495            }
1496            # Collect the limits (if set for the axis)
1497            set min [$dataobj hints ${axis}min]
1498            set max [$dataobj hints ${axis}max]
1499            set tag ${label}-min
1500            if { $min != "" && ( ![info exists _limits($tag)] ||
1501                                   $_limits($tag) > $min ) } {
1502                set _limits($tag) $min
1503            }
1504            set tag ${label}-max
1505            if { $max != "" && (![info exists _limits($tag)] ||
1506                                  $_limits($tag) < $max) } {
1507                set _limits($tag) $max
1508            }
1509            if  { [$dataobj hints ${axis}scale] == "log" } {
1510                set tag ${label}-log
1511                set _limits($tag) 1
1512            }
1513            if { ![info exists _label2axis($label)] } {
1514                switch [incr anum($axis)] {
1515                    1 { set axisName $axis }
1516                    2 { set axisName ${axis}2 }
1517                    default {
1518                        set axis $axis$anum($axis)
1519                        catch {$g axis create $axisName}
1520                    }
1521                }
1522                $g axis configure $axisName -title $label -hide no \
1523                    -checklimits no -showticks yes
1524                set _label2axis($label) $axisName
1525
1526                # If this axis has a description, add it as a tooltip
1527                set desc [string trim [$dataobj hints ${axis}desc]]
1528                Rappture::Tooltip::text $g-$axisName $desc
1529            }
1530        }
1531    }
1532    # Next, set the axes based on what we've found.
1533    foreach label [array names _label2axis] {
1534        set logscale [info exists _limits(${label}-log)]
1535        set amin ""
1536        if { [info exists _limits(${label}-min)] } {
1537            set amin $_limits(${label}-min)
1538        }
1539        set amax ""
1540        if { [info exists _limits(${label}-max)] } {
1541            set amax $_limits(${label}-max)
1542        }
1543        set axis $_label2axis($label)
1544        $g axis configure $axis \
1545            -hide no -checklimits no \
1546            -command [itcl::code $this GetFormattedValue $axis] \
1547            -min $amin -max $amax -logscale $logscale
1548        $g axis bind $axis <Enter> \
1549            [itcl::code $this Axis hilite $axis on]
1550        $g axis bind $axis <Leave> \
1551            [itcl::code $this Axis hilite $axis off]
1552        $g axis bind $axis <ButtonPress-1> \
1553            [itcl::code $this Axis click $axis %x %y]
1554        $g axis bind $axis <B1-Motion> \
1555            [itcl::code $this Axis drag $axis %x %y]
1556        $g axis bind $axis <ButtonRelease-1> \
1557            [itcl::code $this Axis release $axis %x %y]
1558        $g axis bind $axis <KeyPress> \
1559            [list ::Rappture::Tooltip::tooltip cancel]
1560    }
1561
1562    foreach dataobj $dlist {
1563        SetElements $dataobj
1564    }
1565    ResetLegend
1566}
1567
1568#
1569# SetElements --
1570#
1571#       This procedure loads each data objects specified into the
1572#       graph.  The data object may already be loaded (from the "add"
1573#       method which gets called first).   The graph elements that
1574#       are created, are hidden.  This allows the graph to account
1575#       for all datasets, even those not currently being displayed.
1576#
1577itcl::body Rappture::XyResult::SetElements { dataobj {settings ""} } {
1578    set g $itk_component(plot)
1579
1580    array set attrs [$dataobj hints style]
1581    array set attrs $settings
1582    set type [$dataobj hints type]
1583    if { $type == "" } {
1584        set type "line"
1585    }
1586
1587    # Now fix attributes to a more usable form for the graph.
1588
1589    # Convert -linestyle to BLT -dashes
1590    if { ![info exists attrs(-linestyle)] } {
1591        set dashes {}
1592    } else {
1593        switch -- $attrs(-linestyle) {
1594            dashed  { set dashes {4 4} }
1595            dotted  { set dashes {2 4} }
1596            default { set dashes {}    }
1597        }
1598    }
1599    if { ![info exists attrs(-barwidth)] } {
1600        set barwidth 1.0
1601    } else {
1602        set barwidth $attrs(-barwidth)
1603    }
1604    if { ![info exists attrs(-width)] } {
1605        set linewidth 1
1606    } else {
1607        set linewidth $attrs(-width)
1608    }
1609    set _dataobj2raise($dataobj) [info exists attrs(-raise)]
1610    if { ![info exists attrs(-simulation)] } {
1611        set sim 0
1612    } else {
1613        set sim $attrs(-simulation)
1614    }
1615    set _dataobj2sim($dataobj) $sim
1616
1617    foreach {mapx mapy} [GetAxes $dataobj] break
1618    set label [$dataobj hints label]
1619
1620    foreach cname [$dataobj components] {
1621        set tag $dataobj-$cname
1622        set xv [$dataobj mesh $cname]
1623        set yv [$dataobj values $cname]
1624        set xev [$dataobj xErrorValues $cname]
1625        set yev [$dataobj yErrorValues $cname]
1626        if {([$xv length] <= 1) || ($linewidth == 0)} {
1627            set sym square
1628            set pixels 2
1629        } else {
1630            set sym ""
1631            set pixels 6
1632        }
1633        if { ![info exists _comp2elem($tag)] } {
1634            set elem "$type[incr _nextElement]"
1635            set _elem2comp($elem) $tag
1636            set _comp2elem($tag) $elem
1637            set found($_comp2elem($tag)) 1
1638            lappend label2elem($label) $elem
1639            switch -- $type {
1640                "line" {
1641                    $g line create $elem \
1642                        -x $xv -y $yv \
1643                        -symbol $sym \
1644                        -pixels $pixels \
1645                        -linewidth $linewidth \
1646                        -label $label \
1647                        -dashes $dashes \
1648                        -mapx $mapx \
1649                        -mapy $mapy \
1650                        -hide yes \
1651                        -xerror $xev -yerror $yev
1652                }
1653                "scatter" {
1654                    $g line create $elem \
1655                        -x $xv -y $yv \
1656                        -symbol square \
1657                        -pixels 2 \
1658                        -linewidth 0 \
1659                        -label $label \
1660                        -dashes $dashes \
1661                        -mapx $mapx \
1662                        -mapy $mapy \
1663                        -hide yes \
1664                        -xerror $xev -yerror $yev
1665                }
1666                "bar" {
1667                    $g bar create $elem \
1668                        -x $xv -y $yv \
1669                        -barwidth $barwidth \
1670                        -label $label \
1671                        -mapx $mapx \
1672                        -mapy $mapy \
1673                        -hide yes \
1674                        -xerror $xev -yerror $yev
1675                }
1676            }
1677        } else {
1678            $g element configure $_comp2elem($tag) -mapx $mapx -mapy $mapy
1679        }
1680    }
1681}
1682
Note: See TracBrowser for help on using the repository browser.