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

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

fix axis mappings for alternate axes. fix tooltip for elements with alternate axes.

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