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

Last change on this file since 4203 was 4203, checked in by gah, 10 years ago

bug fix: markers not created in xyresult (wrong class name). bug fix: add error bar methods to field (as a fake curve)

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