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

Last change on this file since 3791 was 3791, checked in by mmc, 11 years ago

Fixes for the xyresult reported on kronig_penney tool. The new bar/scatter
mode options were causing problems with the caching of existing segments.
Fixed the color options to work properly instead of showing black elements.
Fixed scatter plots so that colors are set correctly when you switch back
and forth between two results. Fixed dashed line styles so that they are
interpreted correctly. The kronig_penney tool seems to be working better now.

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