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

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