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

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

bugfix: set log scale from hint in XML

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