source: branches/uq/gui/scripts/xyresult.tcl @ 5679

Last change on this file since 5679 was 5679, checked in by ldelgass, 9 years ago

Full merge 1.3 branch to uq branch to sync. Fixed partial subdirectory merge
by removing mergeinfo from lang/python/Rappture directory.

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