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

Last change on this file since 3868 was 3803, checked in by gah, 11 years ago

fix limits on axis

File size: 58.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            -rightmargin 10
177    } {
178        keep -background -foreground -cursor -font
179    }
180    pack $itk_component(plot) -expand yes -fill both
181
182    $itk_component(plot) pen configure activeLine \
183        -symbol square -pixels 3 -linewidth 2 \
184        -outline black -fill red -color black
185
186    # Add bindings so you can mouse over points to see values:
187    #
188    $itk_component(plot) element bind all <Enter> \
189        [itcl::code $this Hilite at %x %y]
190    $itk_component(plot) element bind all <Motion> \
191        [itcl::code $this Hilite at %x %y]
192    $itk_component(plot) element bind all <Leave> \
193        [itcl::code $this Hilite off %x %y]
194
195    $itk_component(plot) legend configure -hide yes
196
197    #
198    # Add legend for editing hidden/elements:
199    #
200    set inner [$itk_component(main) insert end \
201        -title "Legend" \
202        -icon [Rappture::icon wrench]]
203    $inner configure -borderwidth 4
204
205    itk_component add legend {
206        Rappture::XyLegend $inner.legend $itk_component(plot)
207    }
208    pack $itk_component(legend) -expand yes -fill both
209    after idle [itcl::code $this ResetLegend]
210
211    # quick-and-dirty zoom functionality, for now...
212    Blt_ZoomStack $itk_component(plot)
213    eval itk_initialize $args
214
215    set _hilite(elem) ""
216}
217
218# ----------------------------------------------------------------------
219# DESTRUCTOR
220# ----------------------------------------------------------------------
221itcl::body Rappture::XyResult::destructor {} {
222}
223
224# ----------------------------------------------------------------------
225# USAGE: add <dataobj> ?<settings>?
226#
227# Clients use this to add a dataobj to the plot.  The optional <settings>
228# are used to configure the plot.  Allowed settings are -color,
229# -brightness, -width, -linestyle and -raise.
230# ----------------------------------------------------------------------
231itcl::body Rappture::XyResult::add {dataobj {settings ""}} {
232    #puts stderr "add: dataobj=$dataobj settings=$settings"
233    set g $itk_component(plot)
234    SetElements $dataobj $settings
235
236    array set attrs $settings
237
238    # Colors have to be set/reset here because of "-brightness" and "auto".
239    # Colors can't be overriden by the user.
240
241    # If the color is "auto", then select a color from -autocolors
242    if { ![info exists attrs(-color)] } {
243        set color "auto"
244    } else {
245        set color $attrs(-color)
246    }
247    if { $color == "auto" || $color == "autoreset" } {
248        if { $color == "autoreset" } {
249            set _nextColorIndex 0
250        }
251        set color [lindex $itk_option(-autocolors) $_nextColorIndex]
252        if { "" == $color} {
253            set color black
254        }
255        # Set up for next auto color
256        incr _nextColorIndex
257        if { $_nextColorIndex >= [llength $itk_option(-autocolors)] } {
258            set _nextColorIndex 0
259        }
260    }
261    # If -brightness is set, then update the color.
262    if { [info exists attrs(-brightness)] } {
263        set brightness $attrs(-brightness)
264        set color [Rappture::color::brightness $color $brightness]
265        set bg [$itk_component(plot) cget -plotbackground]
266        foreach {h s v} [Rappture::color::RGBtoHSV $bg] break
267        if {$v > 0.5} {
268            set color [Rappture::color::brightness_max $color 0.8]
269        } else {
270            set color [Rappture::color::brightness_min $color 0.2]
271        }
272    }
273    set type [$dataobj hints type]
274    foreach cname [$dataobj components] {
275        set tag $dataobj-$cname
276        set elem $_comp2elem($tag)
277        if { $type == "bar" } {
278            $g bar configure $elem -foreground $color -background $color \
279                -hide no
280        } else {
281            $g line configure $elem -color $color -hide no
282        }
283        if { [lsearch $_viewable $elem] < 0 } {
284            lappend _viewable $elem
285        }
286    }
287    if { [$dataobj info class] == "Rappture::Curve" } {
288        BuildMarkers $dataobj $elem
289    }
290    $_dispatcher event -idle !rebuild
291}
292
293# ----------------------------------------------------------------------
294# USAGE: get
295#
296# Clients use this to query the list of objects being plotted, in
297# order from bottom to top of this result.
298# ----------------------------------------------------------------------
299itcl::body Rappture::XyResult::get {} {
300    # put the dataobj list in order according to -raise options
301    set bottom {}
302    set top {}
303    foreach obj $_dlist {
304        if {[info exists _dataobj2raise($obj)] && $_dataobj2raise($obj)} {
305            lappend top $obj
306        } else {
307            lappend bottom $obj
308        }
309    }
310    set _dlist [concat $bottom $top]
311    return $_dlist
312}
313
314# ----------------------------------------------------------------------
315# USAGE: delete ?<dataobj1> <dataobj2> ...?
316#
317# Clients use this to delete a dataobj from the plot.  If no dataobjs
318# are specified, then all dataobjs are deleted.
319# ----------------------------------------------------------------------
320itcl::body Rappture::XyResult::delete {args} {
321    #puts stderr "XyResult::delete args=$args"
322    set g $itk_component(plot)
323
324    # First try to create list of elements from the dataobjs specified
325    set elemlist {}
326    foreach dataobj $args {
327        foreach cname [$dataobj components] {
328            set tag $dataobj-$cname
329            if { [info exists _comp2elem($tag)] } {
330                lappend elemlist $_comp2elem($tag)
331            }
332        }
333    }
334    # If no dataobjs were specified then hide all elements.
335    if { [llength $elemlist] == 0 } {
336        set elemlist [$g element names]
337    }
338    # Hide all elements specified by their dataobjs
339    foreach elem $elemlist {
340        $g element configure $elem -hide yes
341        set i [lsearch $_viewable $elem]
342        if { $i >= 0 } {
343            set _viewable [lreplace $_viewable $i $i]
344        }
345    }
346}
347
348# ----------------------------------------------------------------------
349# USAGE: scale ?<dataobj1> <dataobj2> ...?
350#
351# Sets the default limits for the overall plot according to the
352# limits of the data for all of the given <dataobj> objects.  This
353# accounts for all dataobjs--even those not showing on the screen.
354# Because of this, the limits are appropriate for all dataobjs as
355# the user scans through data in the ResultSet viewer.
356# ----------------------------------------------------------------------
357itcl::body Rappture::XyResult::scale {args} {
358    #puts stderr "XyResult::scale args=$args"
359    set _dlist $args
360    BuildGraph $args
361}
362
363# ----------------------------------------------------------------------
364# USAGE: download coming
365# USAGE: download controls <downloadCommand>
366# USAGE: download now
367#
368# Clients use this method to create a downloadable representation
369# of the plot.  Returns a list of the form {ext string}, where
370# "ext" is the file extension (indicating the type of data) and
371# "string" is the data itself.
372# ----------------------------------------------------------------------
373itcl::body Rappture::XyResult::download {option args} {
374    switch $option {
375        coming {
376            # nothing to do
377        }
378        controls {
379            set popup .xyresultdownload
380            if {![winfo exists .xyresultdownload]} {
381                # if we haven't created the popup yet, do it now
382                Rappture::Balloon $popup \
383                    -title "[Rappture::filexfer::label downloadWord] as..."
384                set inner [$popup component inner]
385                label $inner.summary -text "" -anchor w
386                pack $inner.summary -side top
387                radiobutton $inner.csv -text "Data as Comma-Separated Values" \
388                    -variable Rappture::XyResult::_downloadPopup(format) \
389                    -value csv
390                pack $inner.csv -anchor w
391                radiobutton $inner.image -text "Image (PS/PDF/PNG/JPEG)" \
392                    -variable Rappture::XyResult::_downloadPopup(format) \
393                    -value image
394                pack $inner.image -anchor w
395                button $inner.go -text [Rappture::filexfer::label download] \
396                    -command [lindex $args 0]
397                pack $inner.go -side bottom -pady 4
398            } else {
399                set inner [$popup component inner]
400            }
401            set num [llength [get]]
402            set num [expr {($num == 1) ? "1 result" : "$num results"}]
403            $inner.summary configure -text "[Rappture::filexfer::label downloadWord] $num in the following format:"
404            update idletasks ;# fix initial sizes
405            return $popup
406        }
407        now {
408            set popup .xyresultdownload
409            if {[winfo exists .xyresultdownload]} {
410                $popup deactivate
411            }
412            switch -- $_downloadPopup(format) {
413                csv {
414                    # reverse the objects so the selected data appears on top
415                    set dlist ""
416                    foreach dataobj [get] {
417                        set dlist [linsert $dlist 0 $dataobj]
418                    }
419
420                    # generate the comma-separated value data for these objects
421                    set csvdata ""
422                    foreach dataobj $dlist {
423                        append csvdata "[string repeat - 60]\n"
424                        append csvdata " [$dataobj hints label]\n"
425                        if {[info exists _dataobj2desc($dataobj)]
426                            && [llength [split $_dataobj2desc($dataobj) \n]] > 1} {
427                            set indent "for:"
428                            foreach line [split $_dataobj2desc($dataobj) \n] {
429                                append csvdata " $indent $line\n"
430                                set indent "    "
431                            }
432                        }
433                        append csvdata "[string repeat - 60]\n"
434
435                        append csvdata "[$dataobj hints xlabel], [$dataobj hints ylabel]\n"
436                        set first 1
437                        foreach comp [$dataobj components] {
438                            if {!$first} {
439                                # blank line between components
440                                append csvdata "\n"
441                            }
442                            set xv [$dataobj mesh $comp]
443                            set yv [$dataobj values $comp]
444                            foreach x [$xv range 0 end] y [$yv range 0 end] {
445                                append csvdata [format "%20.15g, %20.15g\n" $x $y]
446                            }
447                            set first 0
448                        }
449                        append csvdata "\n"
450                    }
451                    return [list .txt $csvdata]
452                }
453                image {
454                    set popup .xyprintdownload
455                    if { ![winfo exists $popup] } {
456                        # Create a popup for the print dialog
457                        Rappture::Balloon $popup -title "Save as image..."
458                        set inner [$popup component inner]
459                        # Create the print dialog widget and add it to the
460                        # balloon popup.
461                        Rappture::XyPrint $inner.print
462                        $popup configure \
463                            -deactivatecommand [list $inner.print reset]
464                        blt::table $inner 0,0 $inner.print -fill both
465                    }
466                    update
467                    # Activate the popup and call for the output.
468                    foreach { widget toolName plotName } $args break
469                    $popup activate $widget left
470                    set inner [$popup component inner]
471                    set output [$inner.print print $itk_component(plot) \
472                                    $toolName $plotName]
473                    $popup deactivate
474                    return $output
475                }
476            }
477        }
478        default {
479            error "bad option \"$option\": should be coming, controls, now"
480        }
481    }
482}
483
484itcl::body Rappture::XyResult::BuildMarkers { dataobj elem } {
485    set g $itk_component(plot)
486
487    foreach m [$dataobj xmarkers] {
488        foreach {at label style} $m break
489        set id [$g marker create line -coords [list $at $ymin $at $ymax]]
490        $g marker bind $id <Enter> \
491            [itcl::code $this EnterMarker $g x-$label $at $ymin $at]
492        $g marker bind $id <Leave> \
493            [itcl::code $this LeaveMarker $g x-$label]
494        set options [GetLineMarkerOptions $style]
495        $g marker configure $id -element $elem
496        if { $options != "" } {
497            eval $g marker configure $id $options
498        }
499        if { $label != "" } {
500            set id [$g marker create text -anchor nw \
501                        -text $label -coords [list $at $ymax]]
502            $g marker configure $id -element $elem
503            set options [GetTextMarkerOptions $style]
504            if { $options != "" } {
505                eval $g marker configure $id $options
506            }
507        }
508    }
509    foreach m [$dataobj ymarkers] {
510        foreach {at label style} $m break
511        set id [$g marker create line -coords [list $xmin $at $xmax $at]]
512        $g marker configure $id -element $elem
513        $g marker bind $id <Enter> \
514            [itcl::code $this EnterMarker $g y-$label $at $xmin $at]
515        $g marker bind $id <Leave> \
516            [itcl::code $this LeaveMarker $g y-$label]
517        set options [GetLineMarkerOptions $style]
518        if { $options != "" } {
519            eval $g marker configure $id $options
520        }
521        if { $label != "" } {
522            set id [$g marker create text -anchor se \
523                        -text $label -coords [list $xmax $at]]
524            $g marker configure $id -element $elem
525            set options [GetTextMarkerOptions $style]
526            if { $options != "" } {
527                eval $g marker configure $id $options
528            }
529        }
530    }
531}
532
533# ----------------------------------------------------------------------
534# USAGE: Rebuild
535#
536#       Called automatically whenever something changes that affects the
537# data in the widget.  Clears any existing data and rebuilds the
538# widget to display new data.
539# ----------------------------------------------------------------------
540itcl::body Rappture::XyResult::Rebuild {} {
541    ResetLegend
542    # Fix raise/lower elements
543}
544
545# ----------------------------------------------------------------------
546# USAGE: ResetLimits
547#
548# Used internally to apply automatic limits to the axes for the
549# current plot.
550# ----------------------------------------------------------------------
551itcl::body Rappture::XyResult::ResetLimits {} {
552    set g $itk_component(plot)
553
554    foreach axis [$g axis names] {
555        $g axis configure $axis -min "" -max ""
556    }
557}
558
559# ----------------------------------------------------------------------
560# USAGE: ResetLegend
561#
562# Used internally to apply automatic limits to the axes for the
563# current plot.
564# ----------------------------------------------------------------------
565itcl::body Rappture::XyResult::ResetLegend {} {
566    update idletasks
567
568    set g $itk_component(plot)
569    # Fix duplicate labels by appending the simulation number
570    # Collect the labels from all the viewable elements.
571    set above {}
572    set below {}
573    foreach elem $_viewable {
574        foreach {dataobj cname} [split $_elem2comp($elem) -] break
575        set label [$dataobj hints label]
576        lappend label2elem($label) $elem
577        if { $_dataobj2raise($dataobj) } {
578            lappend $g element raise $elem
579        }
580    }
581    # Then relabel elements with the same label, using the simulation number.
582    foreach label [array names label2elem] {
583        foreach elem $label2elem($label) {
584            if { [llength $label2elem($label)] == 1 } {
585                $g element configure $elem -label $label
586                continue
587            }
588            foreach {dataobj cname} [split $_elem2comp($elem) -] break
589            set sim $_dataobj2sim($dataobj)
590            set elabel [format "%s \#%d" $label $sim]
591            $g element configure $elem -label $elabel
592        }
593    }       
594    $itk_component(legend) reset $_viewable
595}
596
597
598# ----------------------------------------------------------------------
599# USAGE: Zoom reset
600#
601# Called automatically when the user clicks on one of the zoom
602# controls for this widget.  Changes the zoom for the current view.
603# ----------------------------------------------------------------------
604itcl::body Rappture::XyResult::Zoom {option args} {
605    switch -- $option {
606        reset {
607            ResetLimits
608            Rappture::Logger::log curve zoom -reset
609        }
610    }
611}
612
613# ----------------------------------------------------------------------
614# USAGE: Hilite <state> <x> <y>
615#
616# Called automatically when the user brushes one of the elements
617# on the plot.  Causes the element to highlight and a tooltip to
618# pop up with element info.
619# ----------------------------------------------------------------------
620itcl::body Rappture::XyResult::Hilite {state x y} {
621    set g $itk_component(plot)
622    set elem ""
623 
624    # Peek inside of Blt_ZoomStack package to see if we're currently in the
625    # middle of a zoom selection.
626    if {[info exists ::zoomInfo($g,corner)] && $::zoomInfo($g,corner) == "B" } {
627        return;
628    }
629    set tip ""
630    if {$state == "at"} {
631        if {[$g element closest $x $y info -interpolate yes]} {
632            # for dealing with xy line plots
633            set elem $info(name)
634
635            # Some elements are generated dynamically and therefore will
636            # not have a data object associated with them.
637            set mapx [$g element cget $elem -mapx]
638            set mapy [$g element cget $elem -mapy]
639            if {[info exists _elem2comp($elem)]} {
640                foreach {dataobj cname} [split $_elem2comp($elem) -] break
641                foreach {mapx mapy} [GetAxes $dataobj] break
642            }
643
644            # search again for an exact point -- this time don't interpolate
645            set tip ""
646            array unset info
647            if {[$g element closest $x $y info -interpolate no]
648                  && $info(name) == $elem} {
649
650                set x [$g axis transform $mapx $info(x)]
651                set y [$g axis transform $mapy $info(y)]
652               
653                if {[info exists _elem2comp($elem)]} {
654                    foreach {dataobj cname} [split $_elem2comp($elem) -] break
655                    set yunits [$dataobj hints yunits]
656                    set xunits [$dataobj hints xunits]
657                } else {
658                    set xunits ""
659                    set yunits ""
660                }
661                set tip [$g element cget $elem -label]
662                set yval [GetFormattedValue y $g $info(y)]
663                append tip "\n$yval$yunits"
664                set xval [GetFormattedValue x $g $info(x)]
665                append tip " @ $xval$xunits"
666                set tip [string trim $tip]
667            }
668            set state 1
669        } elseif {[$g element closest $x $y info -interpolate no]} {
670            # for dealing with xy scatter plot
671            set elem $info(name)
672
673            # Some elements are generated dynamically and therefore will
674            # not have a data object associated with them.
675            set mapx [$g element cget $elem -mapx]
676            set mapy [$g element cget $elem -mapy]
677            if {[info exists _elem2comp($elem)]} {
678                foreach {dataobj cname} [split $_elem2comp($elem) -] break
679                foreach {mapx mapy} [GetAxes $dataobj] break
680            }
681
682            set tip ""
683            set x [$g axis transform $mapx $info(x)]
684            set y [$g axis transform $mapy $info(y)]
685               
686            if {[info exists _elem2comp($elem)]} {
687                foreach {dataobj cname} [split $_elem2comp($elem) -] break
688                set yunits [$dataobj hints yunits]
689                set xunits [$dataobj hints xunits]
690            } else {
691                set xunits ""
692                set yunits ""
693            }
694            set tip [$g element cget $elem -label]
695            set yval [GetFormattedValue y $g $info(y)]
696            append tip "\n$yval$yunits"
697            set xval [GetFormattedValue x $g $info(x)]
698            append tip " @ $xval$xunits"
699            set tip [string trim $tip]
700            set state 1
701        } else {
702            set state 0
703        }
704    }
705
706    if {$state} {
707        #
708        # Highlight ON:
709        # - activate trace
710        # - multiple axes? dim other axes
711        # - pop up tooltip about data
712        #
713        if { [$g element exists $_hilite(elem)] && $_hilite(elem) != $elem } {
714            $g element deactivate $_hilite(elem)
715            $g crosshairs configure -hide yes
716            Rappture::Tooltip::tooltip cancel
717        }
718        $g element activate $elem
719        set _hilite(elem) $elem
720
721        set mapx [$g element cget $elem -mapx]
722        set mapy [$g element cget $elem -mapy]
723        if {[info exists _elem2comp($elem)]} {
724            foreach {dataobj cname} [split $_elem2comp($elem) -] break
725            foreach {mapx mapy} [GetAxes $dataobj] break
726        }
727        set allx [$g x2axis use]
728        if {[llength $allx] > 0} {
729            lappend allx x  ;# fix main x-axis too
730            foreach axis $allx {
731                if {$axis == $mapx} {
732                    $g axis configure $axis -color $itk_option(-foreground) \
733                        -titlecolor $itk_option(-foreground)
734                } else {
735                    $g axis configure $axis -color $itk_option(-dimcolor) \
736                        -titlecolor $itk_option(-dimcolor)
737                }
738            }
739        }
740        set ally [$g y2axis use]
741        if {[llength $ally] > 0} {
742            lappend ally y  ;# fix main y-axis too
743            foreach axis $ally {
744                if {$axis == $mapy} {
745                    $g axis configure $axis -color $itk_option(-foreground) \
746                        -titlecolor $itk_option(-foreground)
747                } else {
748                    $g axis configure $axis -color $itk_option(-dimcolor) \
749                        -titlecolor $itk_option(-dimcolor)
750                }
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    # rebuild if needed, so we know about the axes
1033    if 0 {
1034        # Don't do this. Given dataobj may be deleted in the rebuild
1035
1036        # rebuild if needed, so we know about the axes
1037        if {[$_dispatcher ispending !rebuild]} {
1038            $_dispatcher cancel !rebuild
1039            $_dispatcher event -now !rebuild
1040        }
1041    }
1042    # what is the x axis?  x? x2? x3? ...
1043    set xlabel [$dataobj hints xlabel]
1044    if {[info exists _label2axis(x-$xlabel)]} {
1045        set mapx $_label2axis(x-$xlabel)
1046    } else {
1047        set mapx "x"
1048    }
1049
1050    # what is the y axis?  y? y2? y3? ...
1051    set ylabel [$dataobj hints ylabel]
1052    if {[info exists _label2axis(y-$ylabel)]} {
1053        set mapy $_label2axis(y-$ylabel)
1054    } else {
1055        set mapy "y"
1056    }
1057
1058    return [list $mapx $mapy]
1059}
1060
1061# ----------------------------------------------------------------------
1062# CONFIGURATION OPTION: -gridcolor
1063# ----------------------------------------------------------------------
1064itcl::configbody Rappture::XyResult::gridcolor {
1065    if {"" == $itk_option(-gridcolor)} {
1066        $itk_component(plot) grid off
1067    } else {
1068        $itk_component(plot) grid configure -color $itk_option(-gridcolor)
1069        $itk_component(plot) grid on
1070    }
1071}
1072
1073# ----------------------------------------------------------------------
1074# CONFIGURATION OPTION: -autocolors
1075# ----------------------------------------------------------------------
1076itcl::configbody Rappture::XyResult::autocolors {
1077    foreach c $itk_option(-autocolors) {
1078        if {[catch {winfo rgb $itk_component(hull) $c}]} {
1079            error "bad color \"$c\""
1080        }
1081    }
1082    if {$_nextColorIndex >= [llength $itk_option(-autocolors)]} {
1083        set _nextColorIndex 0
1084    }
1085}
1086
1087itcl::body Rappture::XyResult::EnterMarker { g name x y text } {
1088    LeaveMarker $g $name
1089    set id [$g marker create text \
1090                -coords [list $x $y] \
1091                -yoffset -1 \
1092                -anchor s \
1093                -text $text]
1094    set _markers($name) $id
1095}
1096
1097itcl::body Rappture::XyResult::LeaveMarker { g name } {
1098    if { [info exists _markers($name)] } {
1099        set id $_markers($name)
1100        $g marker delete $id
1101        unset _markers($name)
1102    }
1103}
1104
1105#
1106# SetAxis --
1107#
1108#       Configures the graph axis with the designated setting using
1109#       the currently stored value.  User-configurable axis settings
1110#       are stored in the _axisPopup variable or in the widgets. This
1111#       routine syncs the graph with that setting.
1112#
1113itcl::body Rappture::XyResult::SetAxis { setting } {
1114    set g $itk_component(plot)
1115    set axis $_axisPopup(axis)
1116    switch -- $setting {
1117        "logscale" {
1118            set bool $_axisPopup(logscale)
1119            $g axis configure $axis -logscale $bool
1120        }
1121        "loose" {
1122            set bool $_axisPopup(loose)
1123            $g axis configure $axis -loose $bool
1124        }
1125        "range" {
1126            set auto $_axisPopup(auto)
1127            set _axisPopup($axis-auto) $auto
1128            if { $auto } {
1129                # Set the axis range automatically
1130                $g axis configure $axis -min "" -max ""
1131            } else {
1132                # Set the axis range from the entry values.
1133                set label $_axisPopup(label)
1134                set min $_axisPopup(${label}-min)
1135                set max $_axisPopup(${label}-max)
1136                $g axis configure $axis -min $min -max $max
1137            }
1138            SetAxisRangeState $axis
1139        }
1140        "format" {
1141            set inner [$itk_component(hull).axes component inner]
1142            set format [$inner.format translate [$inner.format value]]
1143            set _axisPopup($axis-format) $format
1144
1145            # Force the graph to reformat the ticks
1146            set min [$itk_component(plot) axis cget $axis -min]
1147            $g axis configure $axis -min $min
1148        }
1149        "label" {
1150            set label $_axisPopup(label)
1151            $g axis configure $axis -label $label
1152        }
1153        "min" {
1154            set min $_axisPopup(min)
1155            if { [catch { $g axis configure $axis -min $min } msg] != 0 } {
1156                set inner [$itk_component(hull).axes component inner]
1157                Rappture::Tooltip::cue $inner.max $msg
1158                bell
1159                return
1160            }
1161            set label $_axisPopup(label)
1162            set _axisPopup(${label}-min) $min
1163        }
1164        "max" {
1165            set max $_axisPopup(max)
1166            if { [catch { $g axis configure $axis -max $max } msg] != 0 } {
1167                set inner [$itk_component(hull).axes component inner]
1168                Rappture::Tooltip::cue $inner.max $msg
1169                bell
1170                return
1171            }
1172            set label $_axisPopup(label)
1173            set _axisPopup(${label}-max) $max
1174        }
1175    }
1176}
1177
1178#
1179# SetAxisRangeState --
1180#
1181#       Sets the state of widgets controlling the axis range based
1182#       upon whether the automatic or manual setting.  If the
1183#       axis is configure to be automatic, the manual setting widgets
1184#       are disabled.  And vesa-versa the automatic setting widgets
1185#       are dsiabled if the axis is manual.
1186#
1187itcl::body Rappture::XyResult::SetAxisRangeState { axis } {
1188    set inner [$itk_component(hull).axes component inner]
1189    set g $itk_component(plot)
1190
1191    if { $_axisPopup(auto) } {
1192        foreach {min max} [$g axis limits $axis] break
1193        $inner.minl configure -state disabled
1194        $inner.min configure -state disabled
1195        $inner.maxl configure -state disabled
1196        $inner.max configure -state disabled
1197        $inner.loose configure -state normal
1198        $inner.tight configure -state normal
1199    } else {
1200        foreach {min max} [$g axis limits $axis] break
1201        $inner.minl configure -state normal
1202        $inner.min configure -state normal
1203        set _axisPopup(min) [$g axis cget $axis -min]
1204        $inner.maxl configure -state normal
1205        $inner.max configure -state normal
1206        set _axisPopup(max) [$g axis cget $axis -max]
1207        $inner.loose configure -state disabled
1208        $inner.tight configure -state disabled
1209    }
1210}
1211
1212#
1213# BuildAxisPopup --
1214#
1215#       Creates the popup balloon dialog for axes. This routine is
1216#       called only once the first time the user clicks to bring up
1217#       an axis dialog.  It is reused for all other axes. 
1218#
1219itcl::body Rappture::XyResult::BuildAxisPopup { popup } {
1220    Rappture::Balloon $popup -title "Axis Options"
1221    set inner [$itk_component(hull).axes component inner]
1222
1223    label $inner.labell -text "Label:"
1224    entry $inner.label \
1225        -width 15 -highlightbackground $itk_option(-background) \
1226        -textvariable [itcl::scope _axisPopup(label)]
1227
1228    bind $inner.label <Return>   [itcl::code $this SetAxis label]
1229    bind $inner.label <KP_Enter> [itcl::code $this SetAxis label]
1230    bind $inner.label <FocusOut> [itcl::code $this SetAxis label]
1231
1232    label $inner.formatl -text "Format:"
1233    Rappture::Combobox $inner.format -width 15 -editable no
1234    $inner.format choices insert end \
1235        "%.6g"  "Auto"         \
1236        "%.0f"  "X"          \
1237        "%.1f"  "X.X"          \
1238        "%.2f"  "X.XX"         \
1239        "%.3f"  "X.XXX"        \
1240        "%.6f"  "X.XXXXXX"     \
1241        "%.1e"  "X.Xe+XX"      \
1242        "%.2e"  "X.XXe+XX"     \
1243        "%.3e"  "X.XXXe+XX"    \
1244        "%.6e"  "X.XXXXXXe+XX"
1245
1246    bind $inner.format <<Value>> [itcl::code $this SetAxis format]
1247
1248    label $inner.rangel -text "Axis Range:"
1249    radiobutton $inner.auto -text "Automatic" \
1250        -variable [itcl::scope _axisPopup(auto)] -value 1 \
1251        -command [itcl::code $this SetAxis range]
1252    radiobutton $inner.manual -text "Manual" \
1253        -variable [itcl::scope _axisPopup(auto)] -value 0 \
1254        -command [itcl::code $this SetAxis range]
1255
1256    radiobutton $inner.loose -text "loose" \
1257        -variable [itcl::scope _axisPopup(loose)] -value 1 \
1258        -command [itcl::code $this SetAxis loose]
1259    radiobutton $inner.tight -text "tight" \
1260        -variable [itcl::scope _axisPopup(loose)] -value 0 \
1261        -command [itcl::code $this SetAxis loose]
1262
1263    label $inner.minl -text "min"
1264    entry $inner.min \
1265        -width 15 -highlightbackground $itk_option(-background) \
1266        -textvariable [itcl::scope _axisPopup(min)]
1267    bind $inner.min <Return> [itcl::code $this SetAxis min]
1268    bind $inner.min <KP_Enter> [itcl::code $this SetAxis min]
1269    bind $inner.min <FocusOut> [itcl::code $this SetAxis min]
1270
1271    label $inner.maxl -text "max"
1272    entry $inner.max \
1273        -width 15 -highlightbackground $itk_option(-background) \
1274        -textvariable [itcl::scope _axisPopup(max)]
1275    bind $inner.max <Return> [itcl::code $this SetAxis max]
1276    bind $inner.max <KP_Enter> [itcl::code $this SetAxis max]
1277    bind $inner.max <FocusOut> [itcl::code $this SetAxis max]
1278
1279
1280    label $inner.scalel -text "Scale:"
1281    radiobutton $inner.linear -text "linear" \
1282        -variable [itcl::scope _axisPopup(logscale)] -value 0 \
1283        -command [itcl::code $this SetAxis logscale]
1284    radiobutton $inner.log -text "logarithmic" \
1285        -variable [itcl::scope _axisPopup(logscale)] -value 1 \
1286        -command [itcl::code $this SetAxis logscale]
1287
1288    blt::table $inner \
1289        0,0 $inner.labell -anchor w \
1290        0,1 $inner.label -anchor w -fill x  -cspan 3 \
1291        1,0 $inner.formatl -anchor w \
1292        1,1 $inner.format -anchor w -fill x  -cspan 3 \
1293        2,0 $inner.scalel -anchor w \
1294        2,2 $inner.linear -anchor w \
1295        2,3 $inner.log -anchor w \
1296        3,0 $inner.rangel -anchor w \
1297        4,0 $inner.manual -anchor w -padx 4 \
1298        4,2 $inner.minl -anchor e \
1299        4,3 $inner.min -anchor w \
1300        5,2 $inner.maxl -anchor e \
1301        5,3 $inner.max -anchor w \
1302        6,0 $inner.auto -anchor w -padx 4 \
1303        6,2 $inner.tight -anchor w \
1304        6,3 $inner.loose -anchor w \
1305       
1306
1307    blt::table configure $inner r2 -pady 4
1308    blt::table configure $inner c1 -width 20
1309    update
1310}
1311
1312#
1313# ShowAxisPopup --
1314#
1315#       Displays the axis dialog for an axis.  It initializes the
1316#       _axisInfo variables for that axis if necessary.
1317#
1318itcl::body Rappture::XyResult::ShowAxisPopup { axis } {
1319    set g $itk_component(plot)
1320    set popup $itk_component(hull).axes
1321
1322    if { ![winfo exists $popup] } {
1323        BuildAxisPopup $popup
1324    }
1325    set _axisPopup(axis)     $axis
1326    set _axisPopup(label)    [$g axis cget $axis -title]
1327    set _axisPopup(logscale) [$g axis cget $axis -logscale]
1328    set _axisPopup(loose)    [$g axis cget $axis -loose]
1329    if { ![info exists _axisPopup($axis-format)] } {
1330        set inner [$itk_component(hull).axes component inner]
1331        set _axisPopup($axis-format) "%.6g"
1332        set fmts [$inner.format choices get -value]
1333        set i [lsearch -exact $fmts $_axisPopup($axis-format)]
1334        if {$i < 0} { set i 0 }  ;# use Auto choice
1335        $inner.format value [$inner.format choices get -label $i]
1336    }
1337    foreach {min max} [$g axis limits $axis] break
1338    if { $_axisPopup(logscale) } {
1339        set type "log"
1340    } else {
1341        set type "lin"
1342    }
1343    set amin ""
1344    set label $_axisPopup(label)
1345    if { [info exists _limits(${label}-min)] } {
1346        set amin $_limits(${label}-min)
1347    }
1348    set amax ""
1349    if { [info exists _limits(${label}-max)] } {
1350        set amax $_limits(${label}-max)
1351    }
1352    set auto 1
1353    if { $amin != "" || $amax != "" } {
1354        set auto 0
1355    }
1356    if { ![info exists _axisPopup($axis-auto)] } {
1357        set _axisPopup($axis-auto) $auto;# Defaults to automatic
1358    }
1359    set _axisPopup(auto)  $_axisPopup($axis-auto)
1360    SetAxisRangeState $axis
1361    if { ![info exists _axisPopup(${label}-min)] } {
1362        if { $amin != "" } {
1363            set _axisPopup(${label}-min) $amin
1364            set _axisPopup(min)   $_axisPopup(${label}-min)
1365            SetAxis min
1366        } else {
1367            set _axisPopup(${label}-min) $min
1368        }
1369    }
1370    if { ![info exists _axisPopup(${label}-max)] } {
1371        if { $amax != "" } {
1372            set _axisPopup(${label}-max) $amax
1373            set _axisPopup(max)   $_axisPopup(${label}-max)
1374            SetAxis max
1375        } else {
1376            set _axisPopup(${label}-max) $max
1377        }
1378    }
1379    set _axisPopup(min)  $_axisPopup(${label}-min)
1380    set _axisPopup(max)  $_axisPopup(${label}-max)
1381    set _axisPopup(axis) $axis
1382
1383    #
1384    # Figure out where the window should pop up.
1385    #
1386    set x [winfo rootx $g]
1387    set y [winfo rooty $g]
1388    set w [winfo width $g]
1389    set h [winfo height $g]
1390    foreach {x0 y0 pw ph} [$g extents plotarea] break
1391    switch -glob -- $axis {
1392        x {
1393            set x [expr {round($x + $x0+0.5*$pw)}]
1394            set y [expr {round($y + $y0+$ph + 0.5*($h-$y0-$ph))}]
1395            set dir "above"
1396        }
1397        x* {
1398            set x [expr {round($x + $x0+0.5*$pw)}]
1399            set dir "below"
1400            set allx [$itk_component(plot) x2axis use]
1401            set max [llength $allx]
1402            set i [lsearch -exact $allx $axis]
1403            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1404        }
1405        y {
1406            set x [expr {round($x + 0.5*$x0)}]
1407            set y [expr {round($y + $y0+0.5*$ph)}]
1408            set dir "right"
1409        }
1410        y* {
1411            set y [expr {round($y + $y0+0.5*$ph)}]
1412            set dir "left"
1413            set ally [$g y2axis use]
1414            set max [llength $ally]
1415            set i [lsearch -exact $ally $axis]
1416            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1417            set x [expr {round($x+$x0+$pw + ($i+0.5)*($w-$x0-$pw)/double($max))}]
1418        }
1419    }
1420    $popup activate @$x,$y $dir
1421}
1422
1423#
1424# GetFormattedValue --
1425#
1426#       Callback routine for the axis format procedure.  It formats the
1427#       axis tick label according to the selected format.  This routine
1428#       is also used to format tooltip values.
1429#
1430itcl::body Rappture::XyResult::GetFormattedValue { axis g value } {
1431    if { [$g axis cget $axis -logscale] ||
1432         ![info exists _axisPopup($axis-format)] } {
1433        set fmt "%.6g"
1434    } else {
1435        set fmt $_axisPopup($axis-format)
1436    }
1437    return [format $fmt $value]
1438}
1439
1440
1441#
1442# BuildGraph --
1443#
1444#       This procedure loads each data objects specified into the
1445#       graph.  The data object may already be loaded (from the "add"
1446#       method which gets called first).   The graph elements that
1447#       are created, are hidden.  This allows the graph to account
1448#       for all datasets, even those not currently being displayed.
1449#       
1450itcl::body Rappture::XyResult::BuildGraph { dlist } {
1451    set g $itk_component(plot)
1452   
1453    foreach label [array names _label2axis] {
1454        set axis $_label2axis($label)
1455        switch -- $axis {
1456            "x" - "x2" - "y" - "y2" {
1457                $g axis configure $axis -hide yes
1458            }
1459            default {
1460                $g axis delete $axis
1461            }
1462        }
1463    }
1464    array unset _label2axis
1465    array unset _limits
1466
1467    # Scan through all objects and create a list of all axes.
1468    # The first x-axis gets mapped to "x".  The second, to "x2".
1469    # Beyond that, we must create new axes "x3", "x4", etc.
1470    # We do the same for y.
1471   
1472    set anum(x) 0
1473    set anum(y) 0
1474    foreach dataobj $dlist {
1475        foreach axis {x y} {
1476            set label [$dataobj hints ${axis}label]
1477            if { $label == "" } {
1478                continue
1479            }
1480            # Collect the limits (if set for the axis)
1481            set min [$dataobj hints ${axis}min]
1482            set max [$dataobj hints ${axis}max]
1483            set tag ${label}-min
1484            if { $min != "" && ( ![info exists _limits($tag)] ||
1485                                   $_limits($tag) > $min ) } {
1486                set _limits($tag) $min
1487            }
1488            set tag ${label}-max
1489            if { $max != "" && (![info exists _limits($tag)] ||
1490                                  $_limits($tag) < $max) } {
1491                set _limits($tag) $max
1492            }
1493            if  { [$dataobj hints ${axis}scale] == "log" } {
1494                set _limits(${axis}log) 1
1495            }
1496            if { ![info exists _label2axis($label)] } {
1497                switch [incr anum($axis)] {
1498                    1 { set axisName $axis }
1499                    2 { set axisName ${axis}2 }
1500                    default {
1501                        set axis $axis$anum($axis)
1502                        catch {$g axis create $axisName}
1503                    }
1504                }
1505                $g axis configure $axisName -title $label -hide no \
1506                    -checklimits no
1507                set _label2axis($label) $axisName
1508               
1509                # If this axis has a description, add it as a tooltip
1510                set desc [string trim [$dataobj hints ${axis}desc]]
1511                Rappture::Tooltip::text $g-$axisName $desc
1512            }
1513        }
1514    }
1515    # Next set the axes based on what we've found.
1516    foreach label [array names _label2axis] {
1517        set logscale [info exists _limits(${label}log)]
1518        set amin ""
1519        if { [info exists _limits(${label}-min)] } {
1520            set amin $_limits(${label}-min)
1521        }
1522        set amax ""
1523        if { [info exists _limits(${label}-max)] } {
1524            set amax $_limits(${label}-max)
1525        }
1526        set axis $_label2axis($label)
1527        $g axis configure $axis \
1528            -hide no -checklimits no \
1529            -command [itcl::code $this GetFormattedValue $axis] \
1530            -min $amin -max $amax -logscale $logscale
1531        $g axis bind $axis <Enter> \
1532            [itcl::code $this Axis hilite $axis on]
1533        $g axis bind $axis <Leave> \
1534            [itcl::code $this Axis hilite $axis off]
1535        $g axis bind $axis <ButtonPress-1> \
1536            [itcl::code $this Axis click $axis %x %y]
1537        $g axis bind $axis <B1-Motion> \
1538            [itcl::code $this Axis drag $axis %x %y]
1539        $g axis bind $axis <ButtonRelease-1> \
1540            [itcl::code $this Axis release $axis %x %y]
1541        $g axis bind $axis <KeyPress> \
1542            [list ::Rappture::Tooltip::tooltip cancel]
1543    }
1544   
1545    foreach dataobj $dlist {
1546        SetElements $dataobj
1547    }
1548    ResetLegend
1549}
1550
1551#
1552# SetElements --
1553#
1554#       This procedure loads each data objects specified into the
1555#       graph.  The data object may already be loaded (from the "add"
1556#       method which gets called first).   The graph elements that
1557#       are created, are hidden.  This allows the graph to account
1558#       for all datasets, even those not currently being displayed.
1559#       
1560itcl::body Rappture::XyResult::SetElements { dataobj {settings ""} } {
1561    set g $itk_component(plot)
1562
1563    array set attrs [$dataobj hints style]
1564    array set attrs $settings
1565    set type [$dataobj hints type]
1566    if { $type == "" } {
1567        set type "line"
1568    }
1569
1570    # Now fix attributes to a more usable form for the graph.
1571
1572    # Convert -linestyle to BLT -dashes
1573    if { ![info exists attrs(-linestyle)] } {
1574        set dashes {}
1575    } else {
1576        switch -- $attrs(-linestyle) {
1577            dashed  { set dashes {4 4} }
1578            dotted  { set dashes {2 4} }
1579            default { set dashes {}    }
1580        }
1581    }
1582    if { ![info exists attrs(-barwidth)] } {
1583        set barwidth 1.0
1584    } else {
1585        set barwidth $attrs(-barwidth)
1586    }
1587    if { ![info exists attrs(-width)] } {
1588        set linewidth 1
1589    } else {
1590        set linewidth $attrs(-width)
1591    }
1592    set _dataobj2raise($dataobj) [info exists attrs(-raise)]
1593    if { ![info exists attrs(-simulation)] } {
1594        set sim 0
1595    } else {
1596        set sim $attrs(-simulation)
1597    }
1598    set _dataobj2sim($dataobj) $sim
1599
1600    foreach {mapx mapy} [GetAxes $dataobj] break
1601    set label [$dataobj hints label]
1602
1603    foreach cname [$dataobj components] {
1604        set tag $dataobj-$cname
1605        set xv [$dataobj mesh $cname]
1606        set yv [$dataobj values $cname]
1607
1608        if {([$xv length] <= 1) || ($linewidth == 0)} {
1609            set sym square
1610            set pixels 2
1611        } else {
1612            set sym ""
1613            set pixels 6
1614        }
1615        if { ![info exists _comp2elem($tag)] } {
1616            set elem "$type[incr _nextElement]"
1617            set _elem2comp($elem) $tag
1618            set _comp2elem($tag) $elem
1619            set found($_comp2elem($tag)) 1
1620            lappend label2elem($label) $elem
1621            switch -- $type {
1622                "line" {
1623                    $g line create $elem \
1624                        -x $xv -y $yv \
1625                        -symbol $sym \
1626                        -pixels $pixels \
1627                        -linewidth $linewidth \
1628                        -label $label \
1629                        -dashes $dashes \
1630                        -mapx $mapx \
1631                        -mapy $mapy \
1632                        -hide yes
1633                }
1634                "scatter" {               
1635                    $g line create $elem \
1636                        -x $xv -y $yv \
1637                        -symbol square \
1638                        -pixels 2 \
1639                        -linewidth 0 \
1640                        -label $label \
1641                        -dashes $dashes \
1642                        -mapx $mapx \
1643                        -mapy $mapy \
1644                        -hide yes
1645                }
1646                "bar" {
1647                    $g bar create $elem \
1648                        -x $xv -y $yv \
1649                        -barwidth $barwidth \
1650                        -label $label \
1651                        -mapx $mapx \
1652                        -mapy $mapy \
1653                        -hide yes
1654                }
1655            }
1656        } else {
1657            set elem $_comp2elem($tag)
1658            switch -- $type {
1659                "line" {
1660                    $g line configure $elem \
1661                        -symbol $sym \
1662                        -pixels $pixels \
1663                        -linewidth $linewidth \
1664                        -dashes $dashes
1665                }
1666                "bar" {
1667                    $g bar configure $elem \
1668                        -barwidth $barwidth \
1669                        -label $label
1670                }
1671            }
1672        }
1673    }
1674}
Note: See TracBrowser for help on using the repository browser.