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

Last change on this file since 3749 was 3749, checked in by gah, 7 years ago

fix Makefiles (one more time)

File size: 67.8 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 g $itk_component(plot)
384
385
386    set allx [$itk_component(plot) x2axis use]
387    lappend allx x  ;# fix main x-axis too
388
389    set ally [$itk_component(plot) y2axis use]
390    lappend ally y  ;# fix main y-axis too
391    catch {unset _limits}
392   
393    eval $g element delete [$g element names]
394    foreach dataobj $args {
395        set label [$dataobj hints label]
396        foreach {mapx mapy} [GetAxes $dataobj] break
397        foreach comp [$dataobj components] {
398            set xv [$dataobj mesh $comp]
399            set yv [$dataobj values $comp]
400
401            if {[info exists _dataobj2type($dataobj)]} {
402                set type $_dataobj2type($dataobj)
403            } else {
404                set type "line"
405            }
406            if {[info exists _dataobj2barwidth($dataobj)]} {
407                set barwidth $_dataobj2barwidth($dataobj)
408            } else {
409                set barwidth 1.0
410            }
411            if {[info exists _dataobj2width($dataobj)]} {
412                set lwidth $_dataobj2width($dataobj)
413            } else {
414                set lwidth 2
415            }
416            if {([$xv length] <= 1) || ($lwidth == 0)} {
417                set sym square
418                set pixels 2
419            } else {
420                set sym ""
421                set pixels 6
422            }
423            set elem "elem[incr _nextElement]"
424            set _elem2dataobj($elem) $dataobj
425            switch -- $type {
426                "line" - "scatter" {
427                    $g element create $elem -x $xv -y $yv \
428                        -symbol $sym -pixels $pixels -linewidth $lwidth \
429                        -mapx $mapx -mapy $mapy
430                } "bar" {
431                    $g bar create $elem -x $xv -y $yv \
432                        -barwidth $barwidth \
433                        -mapx $mapx -mapy $mapy
434                }
435            }
436        }
437    }
438    foreach axis {x y} {
439        if { [info exists _limits({$axis}log)] } {
440            set type "log"
441            $g axis configure -logscale 1
442        } else {
443            set type "lin"
444        }
445        foreach {min max} [$g axis limits $axis] break
446        set _limits(${axis}-min) $min
447        set _limits(${axis}-max) $max
448        set min [$dataobj hints ${axis}min]
449        set max [$dataobj hints ${axis}max]
450        if {"" != $min } {
451            set _limits(${axis}-min) $min
452        }
453        if {"" != $max } {
454            set _limits(${axis}-max) $max
455        }
456    }
457    eval $g element delete [$g element names]
458    if 0 {
459    foreach dataobj $args {
460        # Find the axes for this dataobj (e.g., {x y2})
461        foreach {map(x) map(y)} [GetAxes $dataobj] break
462        foreach axis {x y} {
463            if {[$dataobj hints ${axis}scale] == "log"} {
464                set _limits(${axis}log) 1
465            }
466            # Get defaults for both linear and log scales
467            foreach type {lin log} {
468                # store results -- ex: _limits(x2log-min)
469                set id $map($axis)$type
470                set min [$dataobj hints ${axis}min]
471                set max [$dataobj hints ${axis}max]
472                if {"" != $min && "" != $max} {
473                    if {![info exists _limits($id-min)]} {
474                        set _limits($id-min) $min
475                        set _limits($id-max) $max
476                    } else {
477                        if {$min < $_limits($id-min)} {
478                            set _limits($id-min) $min
479                        }
480                        if {$max > $_limits($id-max)} {
481                            set _limits($id-max) $max
482                        }
483                    }
484                }
485            }
486        }
487    }
488    }
489    ResetLimits
490}
491
492# ----------------------------------------------------------------------
493# USAGE: download coming
494# USAGE: download controls <downloadCommand>
495# USAGE: download now
496#
497# Clients use this method to create a downloadable representation
498# of the plot.  Returns a list of the form {ext string}, where
499# "ext" is the file extension (indicating the type of data) and
500# "string" is the data itself.
501# ----------------------------------------------------------------------
502itcl::body Rappture::XyResult::download {option args} {
503    switch $option {
504        coming {
505            # nothing to do
506        }
507        controls {
508            set popup .xyresultdownload
509            if {![winfo exists .xyresultdownload]} {
510                # if we haven't created the popup yet, do it now
511                Rappture::Balloon $popup \
512                    -title "[Rappture::filexfer::label downloadWord] as..."
513                set inner [$popup component inner]
514                label $inner.summary -text "" -anchor w
515                pack $inner.summary -side top
516                radiobutton $inner.csv -text "Data as Comma-Separated Values" \
517                    -variable Rappture::XyResult::_downloadPopup(format) \
518                    -value csv
519                pack $inner.csv -anchor w
520                radiobutton $inner.image -text "Image (PS/PDF/PNG/JPEG)" \
521                    -variable Rappture::XyResult::_downloadPopup(format) \
522                    -value image
523                pack $inner.image -anchor w
524                button $inner.go -text [Rappture::filexfer::label download] \
525                    -command [lindex $args 0]
526                pack $inner.go -side bottom -pady 4
527            } else {
528                set inner [$popup component inner]
529            }
530            set num [llength [get]]
531            set num [expr {($num == 1) ? "1 result" : "$num results"}]
532            $inner.summary configure -text "[Rappture::filexfer::label downloadWord] $num in the following format:"
533            update idletasks ;# fix initial sizes
534            return $popup
535        }
536        now {
537            set popup .xyresultdownload
538            if {[winfo exists .xyresultdownload]} {
539                $popup deactivate
540            }
541            switch -- $_downloadPopup(format) {
542                csv {
543                    # reverse the objects so the selected data appears on top
544                    set dlist ""
545                    foreach dataobj [get] {
546                        set dlist [linsert $dlist 0 $dataobj]
547                    }
548
549                    # generate the comma-separated value data for these objects
550                    set csvdata ""
551                    foreach dataobj $dlist {
552                        append csvdata "[string repeat - 60]\n"
553                        append csvdata " [$dataobj hints label]\n"
554                        if {[info exists _dataobj2desc($dataobj)]
555                            && [llength [split $_dataobj2desc($dataobj) \n]] > 1} {
556                            set indent "for:"
557                            foreach line [split $_dataobj2desc($dataobj) \n] {
558                                append csvdata " $indent $line\n"
559                                set indent "    "
560                            }
561                        }
562                        append csvdata "[string repeat - 60]\n"
563
564                        append csvdata "[$dataobj hints xlabel], [$dataobj hints ylabel]\n"
565                        set first 1
566                        foreach comp [$dataobj components] {
567                            if {!$first} {
568                                # blank line between components
569                                append csvdata "\n"
570                            }
571                            set xv [$dataobj mesh $comp]
572                            set yv [$dataobj values $comp]
573                            foreach x [$xv range 0 end] y [$yv range 0 end] {
574                                append csvdata [format "%20.15g, %20.15g\n" $x $y]
575                            }
576                            set first 0
577                        }
578                        append csvdata "\n"
579                    }
580                    return [list .txt $csvdata]
581                }
582                image {
583                    set popup .xyprintdownload
584                    if { ![winfo exists $popup] } {
585                        # Create a popup for the print dialog
586                        Rappture::Balloon $popup -title "Save as image..."
587                        set inner [$popup component inner]
588                        # Create the print dialog widget and add it to the
589                        # balloon popup.
590                        Rappture::XyPrint $inner.print
591                        $popup configure \
592                            -deactivatecommand [list $inner.print reset]
593                        blt::table $inner 0,0 $inner.print -fill both
594                    }
595                    update
596                    # Activate the popup and call for the output.
597                    foreach { widget toolName plotName } $args break
598                    $popup activate $widget left
599                    set inner [$popup component inner]
600                    set output [$inner.print print $itk_component(plot) \
601                                    $toolName $plotName]
602                    $popup deactivate
603                    return $output
604                }
605            }
606        }
607        default {
608            error "bad option \"$option\": should be coming, controls, now"
609        }
610    }
611}
612
613itcl::body Rappture::XyResult::BuildMarkers { dataobj elem } {
614    set g $itk_component(plot)
615
616    foreach m [$dataobj xmarkers] {
617        foreach {at label style} $m break
618        set id [$g marker create line -coords [list $at $ymin $at $ymax]]
619        $g marker bind $id <Enter> \
620            [itcl::code $this EnterMarker $g x-$label $at $ymin $at]
621        $g marker bind $id <Leave> \
622            [itcl::code $this LeaveMarker $g x-$label]
623        set options [GetLineMarkerOptions $style]
624        $g marker configure $id -element $elem
625        if { $options != "" } {
626            eval $g marker configure $id $options
627        }
628        if { $label != "" } {
629            set id [$g marker create text -anchor nw \
630                        -text $label -coords [list $at $ymax]]
631            $g marker configure $id -element $elem
632            set options [GetTextMarkerOptions $style]
633            if { $options != "" } {
634                eval $g marker configure $id $options
635            }
636        }
637    }
638    foreach m [$dataobj ymarkers] {
639        foreach {at label style} $m break
640        set id [$g marker create line -coords [list $xmin $at $xmax $at]]
641        $g marker configure $id -element $elem
642        $g marker bind $id <Enter> \
643            [itcl::code $this EnterMarker $g y-$label $at $xmin $at]
644        $g marker bind $id <Leave> \
645            [itcl::code $this LeaveMarker $g y-$label]
646        set options [GetLineMarkerOptions $style]
647        if { $options != "" } {
648            eval $g marker configure $id $options
649        }
650        if { $label != "" } {
651            set id [$g marker create text -anchor se \
652                        -text $label -coords [list $xmax $at]]
653            $g marker configure $id -element $elem
654            set options [GetTextMarkerOptions $style]
655            if { $options != "" } {
656                eval $g marker configure $id $options
657            }
658        }
659    }
660}
661
662# ----------------------------------------------------------------------
663# USAGE: BuildElementsAndMarkers
664#
665#       This does what "Rebuild" used to.  It (re)creates all the 
666#       the elements and markers for the graph based on the data objects
667#       given.  The axes are also set if min and max have been set for
668#       any data object. 
669# data in the widget.  Clears any existing data and rebuilds the
670# widget to display new data.
671# ----------------------------------------------------------------------
672itcl::body Rappture::XyResult::BuildElementsAndMarkers { dlist } {
673    set g $itk_component(plot)
674
675    # First clear out the widget and hide the axes.
676    eval $g element delete [$g element names]
677    eval $g marker delete [$g marker names]
678    foreach label [array names _label2axis] {
679        set axis $_label2axis($label)
680        switch -- $axis {
681            "x" - "x2" - "y" - "y2" {
682                # Do nothing
683                $g axis configure $axis -hide yes
684            }
685            default {
686                $g axis delete $axis
687            }
688    }
689    array unset _label2axis
690    array unset _limits
691
692    # Scan through all objects and create a list of all axes.
693    # The first x-axis gets mapped to "x".  The second, to "x2".
694    # Beyond that, we must create new axes "x3", "x4", etc.
695    # We do the same for y.
696
697    set anum(x) 0
698    set anum(y) 0
699    foreach dataobj $dlist {
700        foreach axis {x y} {
701            set label [$dataobj hints ${axis}label]
702            if { $label == "" } {
703                continue
704            }
705            # Collect the limits (if set for the axis)
706            set min [$dataobj hints ${axis}min]
707            set max [$dataobj hints ${axis}max]
708            if { $min != "" && (![info exists _limits(${label}-min)] ||
709                                $_limits(${label}-min) > $min) } {
710                set _limits(${label}-min} $min
711            }
712            if { $max != "" && (![info exists _limits(${label}-max)] ||
713                                $_limits(${label}-max) < $max) } {
714                set _limits(${label}-max} $max
715            }
716            if  {[$dataobj hints ${axis}scale] == "log"} {
717                set _limits(${axis}log) 1
718            }
719            if {![info exists _label2axis($label)]} {
720                switch [incr anum($axis)] {
721                    1 { set axisName $axis }
722                    2 { set axisName ${axis}2 }
723                    default {
724                        set axis $axis$anum($axis)
725                        catch {$g axis create $axisName}
726                    }
727                }
728                $g axis configure $axisName -title $label -hide no \
729                    -checklimits no
730                set _label2axis($label) $axisName
731               
732                # if this axis has a description, add it as a tooltip
733                set desc [string trim [$dataobj hints ${axis}desc]]
734                Rappture::Tooltip::text $g-$axisName $desc
735            }
736        }
737    }
738
739    # Next set the axes based on what we've found.
740    foreach label [array names _label2axis] {
741        if { [info exist _limits(${label}log)] } {
742            set logscale 1
743        } else {
744            set logscale 0
745        }
746        set amin ""
747        if { [info exists _limits(${label}-min)] } {
748            set amin $_limits(${label}-min)
749        }
750        set amax ""
751        if { [info exists _limits(${label}-max)] } {
752            set amax $_limits(${label}-max)
753        }
754        set axis $_label2axis($label)
755        $g axis configure $axis \
756            -hide no -checklimits no \
757            -command [itcl::code $this GetFormattedValue $axis] \
758            -min $amin -max $amax -logscale $logscale
759        $g axis bind $axis <Enter> \
760            [itcl::code $this Axis hilite $axis on]
761        $g axis bind $axis <Leave> \
762            [itcl::code $this Axis hilite $axis off]
763        $g axis bind $axis <ButtonPress-1> \
764            [itcl::code $this Axis click $axis %x %y]
765        $g axis bind $axis <B1-Motion> \
766            [itcl::code $this Axis drag $axis %x %y]
767        $g axis bind $axis <ButtonRelease-1> \
768            [itcl::code $this Axis release $axis %x %y]
769        $g axis bind $axis <KeyPress> \
770            [list ::Rappture::Tooltip::tooltip cancel]
771    }
772
773    # Generate all the data elements and markers, but mark them as hidden.
774    # The Rebuild method will un-hide them.
775    set count 0
776    foreach dataobj $dlist {
777        set label [$dataobj hints label]
778        foreach {mapx mapy} [GetAxes $dataobj] break
779        foreach comp [$dataobj components] {
780            set xv [$dataobj mesh $comp]
781            set yv [$dataobj values $comp]
782
783            if {[info exists _dataobj2color($dataobj)]} {
784                set color $_dataobj2color($dataobj)
785            } else {
786                set color black
787            }
788            if {[info exists _dataobj2type($dataobj)]} {
789                set type $_dataobj2type($dataobj)
790            } else {
791                set type "line"
792            }
793            if {[info exists _dataobj2barwidth($dataobj)]} {
794                set barwidth $_dataobj2barwidth($dataobj)
795            } else {
796                set barwidth 1.0
797            }
798            if {[info exists _dataobj2width($dataobj)]} {
799                set lwidth $_dataobj2width($dataobj)
800            } else {
801                set lwidth 2
802            }
803            if {[info exists _dataobj2dashes($dataobj)]} {
804                set dashes $_dataobj2dashes($dataobj)
805            } else {
806                set dashes ""
807            }
808            if {([$xv length] <= 1) || ($lwidth == 0)} {
809                set sym square
810                set pixels 2
811            } else {
812                set sym ""
813                set pixels 6
814            }
815
816            set elem "elem[incr _nextElement]"
817            set _elem2dataobj($elem) $dataobj
818            lappend label2elem($label) $elem
819            switch -- $type {
820                "line" - "scatter" {
821                    $g element create $elem -x $xv -y $yv \
822                        -symbol $sym -pixels $pixels -linewidth $lwidth \
823                        -label $label \
824                        -color $color -dashes $dashes \
825                        -mapx $mapx -mapy $mapy -hide yes
826                } "bar" {
827                    $g bar create $elem -x $xv -y $yv \
828                        -barwidth $barwidth \
829                        -label $label \
830                        -color $color \
831                        -mapx $mapx -mapy $mapy -hide yes
832                }
833            }
834            if { [$dataobj info class] == "Rappture::Curve" } {
835                BuildMarkers $dataobj $elem
836            }
837        }
838    }
839    # Fix duplicate labels by appending the simulation number
840    foreach label [array names label2elem] {
841        if { [llength $label2elem($label)] == 1 } {
842            continue
843        }
844        foreach elem $label2elem($label) {
845            set dataobj $_elem2dataobj($elem)
846            regexp {^::curve(?:Value)?([0-9]+)$} $dataobj match suffix
847            incr suffix
848            set elabel [format "%s \#%d" $label $suffix]
849            $g element configure $elem -label $elabel
850        }
851    }       
852    $itk_component(legend) reset
853}
854
855# ----------------------------------------------------------------------
856# USAGE: Rebuild
857#
858#       Called automatically whenever something changes that affects the
859# data in the widget.  Clears any existing data and rebuilds the
860# widget to display new data.
861# ----------------------------------------------------------------------
862itcl::body Rappture::XyResult::Rebuild {} {
863    set g $itk_component(plot)
864
865    # First clear out the widget
866    eval $g element delete [$g element names]
867    eval $g marker delete [$g marker names]
868
869    foreach axis [$g axis names] {
870        if { [info exist _limits(${axis}log)] } {
871            set type "log"
872            set logscale 1
873        } else {
874            set type "lin"
875            set logscale 0
876        }
877        set amin ""
878        if { [info exists _limits(${axis}-min)] } {
879            set amin $_limits(${axis}-min)
880        }
881        set amax ""
882        if { [info exists _limits(${axis}-max)] } {
883            set amax $_limits(${axis}-max)
884        }
885        $g axis configure $axis \
886            -hide yes -checklimits no \
887            -command [itcl::code $this GetFormattedValue $axis] \
888            -min $amin -max $amax -logscale $logscale
889    }
890    # Presumably you want at least an X-axis and Y-axis displayed.
891    $g xaxis configure -hide no
892    $g yaxis configure -hide no
893    array unset _label2axis
894
895    #
896    # Scan through all objects and create a list of all axes.
897    # The first x-axis gets mapped to "x".  The second, to "x2".
898    # Beyond that, we must create new axes "x3", "x4", etc.
899    # We do the same for y.
900    #
901    set anum(x) 0
902    set anum(y) 0
903    foreach dataobj [get] {
904        foreach ax {x y} {
905            set label [$dataobj hints ${ax}label]
906            if {"" != $label} {
907                if {![info exists _label2axis($ax-$label)]} {
908                    switch [incr anum($ax)] {
909                        1 { set axis $ax }
910                        2 { set axis ${ax}2 }
911                        default {
912                            set axis $ax$anum($ax)
913                            catch {$g axis create $axis}
914                        }
915                    }
916                    $g axis configure $axis -title $label -hide no \
917                        -checklimits no
918                    set _label2axis($ax-$label) $axis
919
920                    # if this axis has a description, add it as a tooltip
921                    set desc [string trim [$dataobj hints ${ax}desc]]
922                    Rappture::Tooltip::text $g-$axis $desc
923                }
924            }
925        }
926    }
927
928    #
929    # All of the extra axes get mapped to the x2/y2 (top/right)
930    # position.
931    #
932    set all ""
933    foreach ax {x y} {
934        lappend all $ax
935
936        set extra ""
937        for {set i 2} {$i <= $anum($ax)} {incr i} {
938            lappend extra ${ax}$i
939        }
940        eval lappend all $extra
941        $g ${ax}2axis use $extra
942        if {$ax == "y"} {
943            $g configure -rightmargin [expr {($extra == "") ? 10 : 0}]
944        }
945    }
946
947    foreach axis $all {
948        $g axis bind $axis <Enter> \
949            [itcl::code $this Axis hilite $axis on]
950        $g axis bind $axis <Leave> \
951            [itcl::code $this Axis hilite $axis off]
952        $g axis bind $axis <ButtonPress-1> \
953            [itcl::code $this Axis click $axis %x %y]
954        $g axis bind $axis <B1-Motion> \
955            [itcl::code $this Axis drag $axis %x %y]
956        $g axis bind $axis <ButtonRelease-1> \
957            [itcl::code $this Axis release $axis %x %y]
958        $g axis bind $axis <KeyPress> \
959            [list ::Rappture::Tooltip::tooltip cancel]
960    }
961
962    #
963    # Plot all of the dataobjs.
964    #
965    set count 0
966    foreach dataobj $_dlist {
967        set label [$dataobj hints label]
968        foreach {mapx mapy} [GetAxes $dataobj] break
969        foreach comp [$dataobj components] {
970            set xv [$dataobj mesh $comp]
971            set yv [$dataobj values $comp]
972
973            if {[info exists _dataobj2color($dataobj)]} {
974                set color $_dataobj2color($dataobj)
975            } else {
976                set color black
977            }
978            if {[info exists _dataobj2type($dataobj)]} {
979                set type $_dataobj2type($dataobj)
980            } else {
981                set type "line"
982            }
983            if {[info exists _dataobj2barwidth($dataobj)]} {
984                set barwidth $_dataobj2barwidth($dataobj)
985            } else {
986                set barwidth 1.0
987            }
988            if {[info exists _dataobj2width($dataobj)]} {
989                set lwidth $_dataobj2width($dataobj)
990            } else {
991                set lwidth 2
992            }
993            if {[info exists _dataobj2dashes($dataobj)]} {
994                set dashes $_dataobj2dashes($dataobj)
995            } else {
996                set dashes ""
997            }
998            if {([$xv length] <= 1) || ($lwidth == 0)} {
999                set sym square
1000                set pixels 2
1001            } else {
1002                set sym ""
1003                set pixels 6
1004            }
1005
1006            set elem "elem[incr _nextElement]"
1007            set _elem2dataobj($elem) $dataobj
1008            lappend label2elem($label) $elem
1009            switch -- $type {
1010                "line" - "scatter" {
1011                    $g element create $elem -x $xv -y $yv \
1012                        -symbol $sym -pixels $pixels -linewidth $lwidth \
1013                        -label $label \
1014                        -color $color -dashes $dashes \
1015                        -mapx $mapx -mapy $mapy
1016                } "bar" {
1017                    $g bar create $elem -x $xv -y $yv \
1018                        -barwidth $barwidth \
1019                        -label $label \
1020                        -color $color \
1021                        -mapx $mapx -mapy $mapy
1022                }
1023            }
1024        }
1025    }
1026
1027    # Fix duplicate labels by appending the simulation number
1028    foreach label [array names label2elem] {
1029        if { [llength $label2elem($label)] == 1 } {
1030            continue
1031        }
1032        foreach elem $label2elem($label) {
1033            set dataobj $_elem2dataobj($elem)
1034            regexp {^::curve(?:Value)?([0-9]+)$} $dataobj match suffix
1035            incr suffix
1036            set elabel [format "%s \#%d" $label $suffix]
1037            $g element configure $elem -label $elabel
1038        }
1039    }       
1040
1041    foreach dataobj $_dlist {
1042        set xmin -Inf
1043        set ymin -Inf
1044        set xmax Inf
1045        set ymax Inf
1046        #
1047        # Create text/line markers for each *axis.marker specified.
1048        #
1049        if { [$dataobj info class] == "Rappture::Curve" } {
1050            foreach m [$dataobj xmarkers] {
1051                foreach {at label style} $m break
1052                set id [$g marker create line \
1053                            -coords [list $at $ymin $at $ymax]]
1054                $g marker bind $id <Enter> \
1055                    [itcl::code $this EnterMarker $g x-$label $at $ymin $at]
1056                $g marker bind $id <Leave> \
1057                    [itcl::code $this LeaveMarker $g x-$label]
1058                set options [GetLineMarkerOptions $style]
1059                if { $options != "" } {
1060                    eval $g marker configure $id $options
1061                }
1062                if { $label != "" } {
1063                    set id [$g marker create text -anchor nw \
1064                                -text $label -coords [list $at $ymax]]
1065                    set options [GetTextMarkerOptions $style]
1066                    if { $options != "" } {
1067                        eval $g marker configure $id $options
1068                    }
1069                }
1070            }
1071            foreach m [$dataobj ymarkers] {
1072                foreach {at label style} $m break
1073                set id [$g marker create line \
1074                            -coords [list $xmin $at $xmax $at]]
1075                $g marker bind $id <Enter> \
1076                    [itcl::code $this EnterMarker $g y-$label $at $xmin $at]
1077                $g marker bind $id <Leave> \
1078                    [itcl::code $this LeaveMarker $g y-$label]
1079                set options [GetLineMarkerOptions $style]
1080                if { $options != "" } {
1081                    eval $g marker configure $id $options
1082                }
1083                if { $label != "" } {
1084                    set id [$g marker create text -anchor se \
1085                                -text $label -coords [list $xmax $at]]
1086                    set options [GetTextMarkerOptions $style]
1087                    if { $options != "" } {
1088                        eval $g marker configure $id $options
1089                    }
1090                }
1091            }
1092        }
1093    }
1094    $itk_component(legend) reset
1095}
1096
1097# ----------------------------------------------------------------------
1098# USAGE: ResetLimits
1099#
1100# Used internally to apply automatic limits to the axes for the
1101# current plot.
1102# ----------------------------------------------------------------------
1103itcl::body Rappture::XyResult::ResetLimits {} {
1104    set g $itk_component(plot)
1105
1106    foreach axis [$g axis names] {
1107        $g axis configure $axis -min "" -max ""
1108    }
1109}
1110
1111# ----------------------------------------------------------------------
1112# USAGE: Zoom reset
1113#
1114# Called automatically when the user clicks on one of the zoom
1115# controls for this widget.  Changes the zoom for the current view.
1116# ----------------------------------------------------------------------
1117itcl::body Rappture::XyResult::Zoom {option args} {
1118    switch -- $option {
1119        reset {
1120            ResetLimits
1121            Rappture::Logger::log curve zoom -reset
1122        }
1123    }
1124}
1125
1126# ----------------------------------------------------------------------
1127# USAGE: Hilite <state> <x> <y>
1128#
1129# Called automatically when the user brushes one of the elements
1130# on the plot.  Causes the element to highlight and a tooltip to
1131# pop up with element info.
1132# ----------------------------------------------------------------------
1133itcl::body Rappture::XyResult::Hilite {state x y} {
1134    set g $itk_component(plot)
1135    set elem ""
1136 
1137    # Peek inside of Blt_ZoomStack package to see if we're currently in the
1138    # middle of a zoom selection.
1139    if {[info exists ::zoomInfo($g,corner)] && $::zoomInfo($g,corner) == "B" } {
1140        return;
1141    }
1142    set tip ""
1143    if {$state == "at"} {
1144        if {[$g element closest $x $y info -interpolate yes]} {
1145            # for dealing with xy line plots
1146            set elem $info(name)
1147
1148            # Some elements are generated dynamically and therefore will
1149            # not have a data object associated with them.
1150            set mapx [$g element cget $elem -mapx]
1151            set mapy [$g element cget $elem -mapy]
1152            if {[info exists _elem2dataobj($elem)]} {
1153                foreach {mapx mapy} [GetAxes $_elem2dataobj($elem)] break
1154            }
1155
1156            # search again for an exact point -- this time don't interpolate
1157            set tip ""
1158            array unset info
1159            if {[$g element closest $x $y info -interpolate no]
1160                  && $info(name) == $elem} {
1161
1162                set x [$g axis transform $mapx $info(x)]
1163                set y [$g axis transform $mapy $info(y)]
1164               
1165                if {[info exists _elem2dataobj($elem)]} {
1166                    set dataobj $_elem2dataobj($elem)
1167                    set yunits [$dataobj hints yunits]
1168                    set xunits [$dataobj hints xunits]
1169                } else {
1170                    set xunits ""
1171                    set yunits ""
1172                }
1173                set tip [$g element cget $elem -label]
1174                set yval [GetFormattedValue y $g $info(y)]
1175                append tip "\n$yval$yunits"
1176                set xval [GetFormattedValue x $g $info(x)]
1177                append tip " @ $xval$xunits"
1178                set tip [string trim $tip]
1179            }
1180            set state 1
1181        } elseif {[$g element closest $x $y info -interpolate no]} {
1182            # for dealing with xy scatter plot
1183            set elem $info(name)
1184
1185            # Some elements are generated dynamically and therefore will
1186            # not have a data object associated with them.
1187            set mapx [$g element cget $elem -mapx]
1188            set mapy [$g element cget $elem -mapy]
1189            if {[info exists _elem2dataobj($elem)]} {
1190                foreach {mapx mapy} [GetAxes $_elem2dataobj($elem)] break
1191            }
1192
1193            set tip ""
1194            set x [$g axis transform $mapx $info(x)]
1195            set y [$g axis transform $mapy $info(y)]
1196               
1197            if {[info exists _elem2dataobj($elem)]} {
1198                set dataobj $_elem2dataobj($elem)
1199                set yunits [$dataobj hints yunits]
1200                set xunits [$dataobj hints xunits]
1201            } else {
1202                set xunits ""
1203                set yunits ""
1204            }
1205            set tip [$g element cget $elem -label]
1206            set yval [GetFormattedValue y $g $info(y)]
1207            append tip "\n$yval$yunits"
1208            set xval [GetFormattedValue x $g $info(x)]
1209            append tip " @ $xval$xunits"
1210            set tip [string trim $tip]
1211            set state 1
1212        } else {
1213            set state 0
1214        }
1215    }
1216
1217    if {$state} {
1218        #
1219        # Highlight ON:
1220        # - activate trace
1221        # - multiple axes? dim other axes
1222        # - pop up tooltip about data
1223        #
1224        if { [$g element exists $_hilite(elem)] && $_hilite(elem) != $elem } {
1225            $g element deactivate $_hilite(elem)
1226            $g crosshairs configure -hide yes
1227            Rappture::Tooltip::tooltip cancel
1228        }
1229        $g element activate $elem
1230        set _hilite(elem) $elem
1231
1232        set mapx [$g element cget $elem -mapx]
1233        set mapy [$g element cget $elem -mapy]
1234        if {[info exists _elem2dataobj($elem)]} {
1235            foreach {mapx mapy} [GetAxes $_elem2dataobj($elem)] break
1236        }
1237        set allx [$g x2axis use]
1238        if {[llength $allx] > 0} {
1239            lappend allx x  ;# fix main x-axis too
1240            foreach axis $allx {
1241                if {$axis == $mapx} {
1242                    $g axis configure $axis -color $itk_option(-foreground) \
1243                        -titlecolor $itk_option(-foreground)
1244                } else {
1245                    $g axis configure $axis -color $itk_option(-dimcolor) \
1246                        -titlecolor $itk_option(-dimcolor)
1247                }
1248            }
1249        }
1250        set ally [$g y2axis use]
1251        if {[llength $ally] > 0} {
1252            lappend ally y  ;# fix main y-axis too
1253            foreach axis $ally {
1254                if {$axis == $mapy} {
1255                    $g axis configure $axis -color $itk_option(-foreground) \
1256                        -titlecolor $itk_option(-foreground)
1257                } else {
1258                    $g axis configure $axis -color $itk_option(-dimcolor) \
1259                        -titlecolor $itk_option(-dimcolor)
1260                }
1261            }
1262        }
1263
1264        if {"" != $tip} {
1265            $g crosshairs configure -hide no -position @$x,$y
1266
1267            if {$x > 0.5*[winfo width $g]} {
1268                if {$x < 4} {
1269                    set tipx "-0"
1270                } else {
1271                    set tipx "-[expr {$x-20}]"  ;# move tooltip to the left
1272                }
1273            } else {
1274                if {$x < -4} {
1275                    set tipx "+0"
1276                } else {
1277                    set tipx "+[expr {$x+20}]"  ;# move tooltip to the right
1278                }
1279            }
1280            if {$y > 0.5*[winfo height $g]} {
1281                if {$y < 4} {
1282                    set tipy "-0"
1283                } else {
1284                    set tipy "-[expr {$y-20}]"  ;# move tooltip to the top
1285                }
1286            } else {
1287                if {$y < -4} {
1288                    set tipy "+0"
1289                } else {
1290                    set tipy "+[expr {$y+20}]"  ;# move tooltip to the bottom
1291                }
1292            }
1293            Rappture::Tooltip::text $g $tip
1294            Rappture::Tooltip::tooltip show $g $tipx,$tipy
1295            Rappture::Logger::log tooltip -for "curve probe -- [string map [list \n " // "] $tip]"
1296        }
1297    } else {
1298        #
1299        # Highlight OFF:
1300        # - deactivate (color back to normal)
1301        # - put all axes back to normal color
1302        # - take down tooltip
1303        #
1304        if { [$g element exists $_hilite(elem)] } {
1305            $g element deactivate $_hilite(elem)
1306        }
1307        set allx [$g x2axis use]
1308        if {[llength $allx] > 0} {
1309            lappend allx x  ;# fix main x-axis too
1310            foreach axis $allx {
1311                $g axis configure $axis -color $itk_option(-foreground) \
1312                    -titlecolor $itk_option(-foreground)
1313            }
1314        }
1315       
1316        set ally [$g y2axis use]
1317        if {[llength $ally] > 0} {
1318            lappend ally y  ;# fix main y-axis too
1319            foreach axis $ally {
1320                $g axis configure $axis -color $itk_option(-foreground) \
1321                    -titlecolor $itk_option(-foreground)
1322            }
1323        }
1324
1325        $g crosshairs configure -hide yes
1326
1327        # only cancel in plotting area or we'll mess up axes
1328        if {[$g inside $x $y]} {
1329            Rappture::Tooltip::tooltip cancel
1330        }
1331
1332        # There is no currently highlighted element
1333        set _hilite(elem) ""
1334    }
1335}
1336
1337# ----------------------------------------------------------------------
1338# USAGE: Axis hilite <axis> <state>
1339#
1340# USAGE: Axis click <axis> <x> <y>
1341# USAGE: Axis drag <axis> <x> <y>
1342# USAGE: Axis release <axis> <x> <y>
1343#
1344# Used internally to handle editing of the x/y axes.  The hilite
1345# operation causes the axis to light up.  The edit operation pops
1346# up a panel with editing options.  The changed operation applies
1347# changes from the panel.
1348# ----------------------------------------------------------------------
1349itcl::body Rappture::XyResult::Axis {option args} {
1350    switch -- $option {
1351        hilite {
1352            if {[llength $args] != 2} {
1353                error "wrong # args: should be \"Axis hilite axis state\""
1354            }
1355            set g $itk_component(plot)
1356            set axis [lindex $args 0]
1357            set state [lindex $args 1]
1358
1359            if {$state} {
1360                $g axis configure $axis \
1361                    -color $itk_option(-activecolor) \
1362                    -titlecolor $itk_option(-activecolor)
1363
1364                set x [expr {[winfo pointerx $g]+4}]
1365                set y [expr {[winfo pointery $g]+4}]
1366                Rappture::Tooltip::tooltip pending $g-$axis @$x,$y
1367            } else {
1368                $g axis configure $axis \
1369                    -color $itk_option(-foreground) \
1370                    -titlecolor $itk_option(-foreground)
1371                Rappture::Tooltip::tooltip cancel
1372            }
1373        }
1374        click {
1375            if {[llength $args] != 3} {
1376                error "wrong # args: should be \"Axis click axis x y\""
1377            }
1378            set axis [lindex $args 0]
1379            set x [lindex $args 1]
1380            set y [lindex $args 2]
1381            set g $itk_component(plot)
1382
1383            set _axis(moved) 0
1384            set _axis(click-x) $x
1385            set _axis(click-y) $y
1386            foreach {min max} [$g axis limits $axis] break
1387            set _axis(min0) $min
1388            set _axis(max0) $max
1389            Rappture::Tooltip::tooltip cancel
1390        }
1391        drag {
1392            if {[llength $args] != 3} {
1393                error "wrong # args: should be \"Axis drag axis x y\""
1394            }
1395            if {![info exists _axis(moved)]} {
1396                return  ;# must have skipped click event -- ignore
1397            }
1398            set axis [lindex $args 0]
1399            set x [lindex $args 1]
1400            set y [lindex $args 2]
1401            set g $itk_component(plot)
1402
1403            if {[info exists _axis(click-x)] && [info exists _axis(click-y)]} {
1404                foreach {x0 y0 pw ph} [$g extents plotarea] break
1405                switch -glob $axis {
1406                  x* {
1407                    set pix $x
1408                    set pix0 $_axis(click-x)
1409                    set pixmin $x0
1410                    set pixmax [expr {$x0+$pw}]
1411                  }
1412                  y* {
1413                    set pix $y
1414                    set pix0 $_axis(click-y)
1415                    set pixmin [expr {$y0+$ph}]
1416                    set pixmax $y0
1417                  }
1418                }
1419                set log [$g axis cget $axis -logscale]
1420                set min $_axis(min0)
1421                set max $_axis(max0)
1422                set dpix [expr {abs($pix-$pix0)}]
1423                set v0 [$g axis invtransform $axis $pixmin]
1424                set v1 [$g axis invtransform $axis [expr {$pixmin+$dpix}]]
1425                if {$log} {
1426                    set v0 [expr {log10($v0)}]
1427                    set v1 [expr {log10($v1)}]
1428                    set min [expr {log10($min)}]
1429                    set max [expr {log10($max)}]
1430                }
1431
1432                if {$pix > $pix0} {
1433                    set delta [expr {$v1-$v0}]
1434                } else {
1435                    set delta [expr {$v0-$v1}]
1436                }
1437                set min [expr {$min-$delta}]
1438                set max [expr {$max-$delta}]
1439                if {$log} {
1440                    set min [expr {pow(10.0,$min)}]
1441                    set max [expr {pow(10.0,$max)}]
1442                }
1443                $g axis configure $axis -min $min -max $max
1444
1445                # move axis, don't edit on release
1446                set _axis(move) 1
1447            }
1448        }
1449        release {
1450            if {[llength $args] != 3} {
1451                error "wrong # args: should be \"Axis release axis x y\""
1452            }
1453            if {![info exists _axis(moved)]} {
1454                return  ;# must have skipped click event -- ignore
1455            }
1456            set axis [lindex $args 0]
1457            set x [lindex $args 1]
1458            set y [lindex $args 2]
1459
1460            if {!$_axis(moved)} {
1461                # small movement? then treat as click -- pop up axis editor
1462                set dx [expr {abs($x-$_axis(click-x))}]
1463                set dy [expr {abs($y-$_axis(click-y))}]
1464                if {$dx < 2 && $dy < 2} {
1465                    ShowAxisPopup $axis
1466                    return
1467                }
1468            }
1469            # one last movement
1470            Axis drag $axis $x $y
1471
1472            # log this change
1473            Rappture::Logger::log curve axis $axis \
1474                -drag [$itk_component(plot) axis limits $axis]
1475
1476            catch {unset _axis}
1477        }
1478        default {
1479            error "bad option \"$option\": should be hilite"
1480        }
1481    }
1482}
1483
1484
1485# ----------------------------------------------------------------------
1486# USAGE: GetLineMarkerOptions <style>
1487#
1488# Used internally to create a list of configuration options specific to the
1489# axis line marker.  The input is a list of name value pairs.  Options that
1490# are not recognized are ignored.
1491# ----------------------------------------------------------------------
1492itcl::body Rappture::XyResult::GetLineMarkerOptions {style} {
1493    array set lineOptions {
1494        "-color"  "-outline"
1495        "-dashes" "-dashes"
1496        "-linecolor" "-outline"
1497        "-linewidth" "-linewidth"
1498    }
1499    set options {}
1500    foreach {name value} $style {
1501        if { [info exists lineOptions($name)] } {
1502            lappend options $lineOptions($name) $value
1503        }
1504    }
1505    return $options
1506}
1507
1508# ----------------------------------------------------------------------
1509# USAGE: GetTextMarkerOptions <style>
1510#
1511# Used internally to create a list of configuration options specific to the
1512# axis text marker.  The input is a list of name value pairs.  Options that
1513# are not recognized are ignored.
1514# ----------------------------------------------------------------------
1515itcl::body Rappture::XyResult::GetTextMarkerOptions {style} {
1516    array set textOptions {
1517        "-color"        "-outline"
1518        "-textcolor"    "-outline"
1519        "-font"         "-font"
1520        "-xoffset"      "-xoffset"
1521        "-yoffset"      "-yoffset"
1522        "-anchor"       "-anchor"
1523        "-rotate"       "-rotate"
1524    }
1525    set options {}
1526    foreach {name value} $style {
1527        if { [info exists textOptions($name)] } {
1528            lappend options $textOptions($name) $value
1529        }
1530    }
1531    return $options
1532}
1533
1534# ----------------------------------------------------------------------
1535# USAGE: GetAxes <dataobj>
1536#
1537# Used internally to figure out the axes used to plot the given
1538# <dataobj>.  Returns a list of the form {x y}, where x is the
1539# x-axis name (x, x2, x3, etc.), and y is the y-axis name.
1540# ----------------------------------------------------------------------
1541itcl::body Rappture::XyResult::GetAxes {dataobj} {
1542    # rebuild if needed, so we know about the axes
1543    if 0 {
1544        # Don't do this. Given dataobj may be deleted in the rebuild
1545
1546        # rebuild if needed, so we know about the axes
1547        if {[$_dispatcher ispending !rebuild]} {
1548            $_dispatcher cancel !rebuild
1549            $_dispatcher event -now !rebuild
1550        }
1551    }
1552    # what is the x axis?  x? x2? x3? ...
1553    set xlabel [$dataobj hints xlabel]
1554    if {[info exists _label2axis(x-$xlabel)]} {
1555        set mapx $_label2axis(x-$xlabel)
1556    } else {
1557        set mapx "x"
1558    }
1559
1560    # what is the y axis?  y? y2? y3? ...
1561    set ylabel [$dataobj hints ylabel]
1562    if {[info exists _label2axis(y-$ylabel)]} {
1563        set mapy $_label2axis(y-$ylabel)
1564    } else {
1565        set mapy "y"
1566    }
1567
1568    return [list $mapx $mapy]
1569}
1570
1571# ----------------------------------------------------------------------
1572# CONFIGURATION OPTION: -gridcolor
1573# ----------------------------------------------------------------------
1574itcl::configbody Rappture::XyResult::gridcolor {
1575    if {"" == $itk_option(-gridcolor)} {
1576        $itk_component(plot) grid off
1577    } else {
1578        $itk_component(plot) grid configure -color $itk_option(-gridcolor)
1579        $itk_component(plot) grid on
1580    }
1581}
1582
1583# ----------------------------------------------------------------------
1584# CONFIGURATION OPTION: -autocolors
1585# ----------------------------------------------------------------------
1586itcl::configbody Rappture::XyResult::autocolors {
1587    foreach c $itk_option(-autocolors) {
1588        if {[catch {winfo rgb $itk_component(hull) $c}]} {
1589            error "bad color \"$c\""
1590        }
1591    }
1592    if {$_autoColorI >= [llength $itk_option(-autocolors)]} {
1593        set _autoColorI 0
1594    }
1595}
1596
1597itcl::body Rappture::XyResult::EnterMarker { g name x y text } {
1598    LeaveMarker $g $name
1599    set id [$g marker create text \
1600                -coords [list $x $y] \
1601                -yoffset -1 \
1602                -anchor s \
1603                -text $text]
1604    set _markers($name) $id
1605}
1606
1607itcl::body Rappture::XyResult::LeaveMarker { g name } {
1608    if { [info exists _markers($name)] } {
1609        set id $_markers($name)
1610        $g marker delete $id
1611        unset _markers($name)
1612    }
1613}
1614
1615#
1616# SetAxis --
1617#
1618#       Configures the graph axis with the designated setting using
1619#       the currently stored value.  User-configurable axis settings
1620#       are stored in the _axisPopup variable or in the widgets. This
1621#       routine syncs the graph with that setting.
1622#
1623itcl::body Rappture::XyResult::SetAxis { setting } {
1624    set g $itk_component(plot)
1625    set axis $_axisPopup(axis)
1626    switch -- $setting {
1627        "logscale" {
1628            set bool $_axisPopup(logscale)
1629            $g axis configure $axis -logscale $bool
1630        }
1631        "loose" {
1632            set bool $_axisPopup(loose)
1633            $g axis configure $axis -loose $bool
1634        }
1635        "range" {
1636            set auto $_axisPopup(auto)
1637            set _axisPopup($axis-auto) $auto
1638            if { $auto } {
1639                # Set the axis range automatically
1640                $g axis configure $axis -min "" -max ""
1641            } else {
1642                # Set the axis range from the entry values.
1643                set min $_axisPopup($axis-min)
1644                set max $_axisPopup($axis-max)
1645                $g axis configure $axis -min $min -max $max
1646            }
1647            SetAxisRangeState $axis
1648        }
1649        "format" {
1650            set inner [$itk_component(hull).axes component inner]
1651            set format [$inner.format translate [$inner.format value]]
1652            set _axisPopup($axis-format) $format
1653
1654            # Force the graph to reformat the ticks
1655            set min [$itk_component(plot) axis cget $axis -min]
1656            $g axis configure $axis -min $min
1657        }
1658        "label" {
1659            set label $_axisPopup(label)
1660            $g axis configure $axis -label $label
1661        }
1662        "min" {
1663            set min $_axisPopup(min)
1664            if { [catch { $g axis configure $axis -min $min } msg] != 0 } {
1665                set inner [$itk_component(hull).axes component inner]
1666                Rappture::Tooltip::cue $inner.max $msg
1667                bell
1668                return
1669            }
1670            set _axisPopup($axis-min) $min
1671        }
1672        "max" {
1673            set max $_axisPopup(max)
1674            if { [catch { $g axis configure $axis -max $max } msg] != 0 } {
1675                set inner [$itk_component(hull).axes component inner]
1676                Rappture::Tooltip::cue $inner.max $msg
1677                bell
1678                return
1679            }
1680            set _axisPopup($axis-max) $max
1681        }
1682    }
1683}
1684
1685#
1686# SetAxisRangeState --
1687#
1688#       Sets the state of widgets controlling the axis range based
1689#       upon whether the automatic or manual setting.  If the
1690#       axis is configure to be automatic, the manual setting widgets
1691#       are disabled.  And vesa-versa the automatic setting widgets
1692#       are dsiabled if the axis is manual.
1693#
1694itcl::body Rappture::XyResult::SetAxisRangeState { axis } {
1695    set inner [$itk_component(hull).axes component inner]
1696    set g $itk_component(plot)
1697
1698    if { $_axisPopup(auto) } {
1699        foreach {min max} [$g axis limits $axis] break
1700        $inner.minl configure -state disabled
1701        $inner.min configure -state disabled
1702        $inner.maxl configure -state disabled
1703        $inner.max configure -state disabled
1704        $inner.loose configure -state normal
1705        $inner.tight configure -state normal
1706    } else {
1707        foreach {min max} [$g axis limits $axis] break
1708        $inner.minl configure -state normal
1709        $inner.min configure -state normal
1710        set _axisPopup(min) [$g axis cget $axis -min]
1711        $inner.maxl configure -state normal
1712        $inner.max configure -state normal
1713        set _axisPopup(max) [$g axis cget $axis -max]
1714        $inner.loose configure -state disabled
1715        $inner.tight configure -state disabled
1716    }
1717}
1718
1719#
1720# BuildAxisPopup --
1721#
1722#       Creates the popup balloon dialog for axes. This routine is
1723#       called only once the first time the user clicks to bring up
1724#       an axis dialog.  It is reused for all other axes. 
1725#
1726itcl::body Rappture::XyResult::BuildAxisPopup { popup } {
1727    Rappture::Balloon $popup -title "Axis Options"
1728    set inner [$itk_component(hull).axes component inner]
1729
1730    label $inner.labell -text "Label:"
1731    entry $inner.label \
1732        -width 15 -highlightbackground $itk_option(-background) \
1733        -textvariable [itcl::scope _axisPopup(label)]
1734
1735    bind $inner.label <Return>   [itcl::code $this SetAxis label]
1736    bind $inner.label <KP_Enter> [itcl::code $this SetAxis label]
1737    bind $inner.label <FocusOut> [itcl::code $this SetAxis label]
1738
1739    label $inner.formatl -text "Format:"
1740    Rappture::Combobox $inner.format -width 15 -editable no
1741    $inner.format choices insert end \
1742        "%.6g"  "Auto"         \
1743        "%.0f"  "X"          \
1744        "%.1f"  "X.X"          \
1745        "%.2f"  "X.XX"         \
1746        "%.3f"  "X.XXX"        \
1747        "%.6f"  "X.XXXXXX"     \
1748        "%.1e"  "X.Xe+XX"      \
1749        "%.2e"  "X.XXe+XX"     \
1750        "%.3e"  "X.XXXe+XX"    \
1751        "%.6e"  "X.XXXXXXe+XX"
1752
1753    bind $inner.format <<Value>> [itcl::code $this SetAxis format]
1754
1755    label $inner.rangel -text "Axis Range:"
1756    radiobutton $inner.auto -text "Automatic" \
1757        -variable [itcl::scope _axisPopup(auto)] -value 1 \
1758        -command [itcl::code $this SetAxis range]
1759    radiobutton $inner.manual -text "Manual" \
1760        -variable [itcl::scope _axisPopup(auto)] -value 0 \
1761        -command [itcl::code $this SetAxis range]
1762
1763    radiobutton $inner.loose -text "loose" \
1764        -variable [itcl::scope _axisPopup(loose)] -value 1 \
1765        -command [itcl::code $this SetAxis loose]
1766    radiobutton $inner.tight -text "tight" \
1767        -variable [itcl::scope _axisPopup(loose)] -value 0 \
1768        -command [itcl::code $this SetAxis loose]
1769
1770    label $inner.minl -text "min"
1771    entry $inner.min \
1772        -width 15 -highlightbackground $itk_option(-background) \
1773        -textvariable [itcl::scope _axisPopup(min)]
1774    bind $inner.min <Return> [itcl::code $this SetAxis min]
1775    bind $inner.min <KP_Enter> [itcl::code $this SetAxis min]
1776    bind $inner.min <FocusOut> [itcl::code $this SetAxis min]
1777
1778    label $inner.maxl -text "max"
1779    entry $inner.max \
1780        -width 15 -highlightbackground $itk_option(-background) \
1781        -textvariable [itcl::scope _axisPopup(max)]
1782    bind $inner.max <Return> [itcl::code $this SetAxis max]
1783    bind $inner.max <KP_Enter> [itcl::code $this SetAxis max]
1784    bind $inner.max <FocusOut> [itcl::code $this SetAxis max]
1785
1786
1787    label $inner.scalel -text "Scale:"
1788    radiobutton $inner.linear -text "linear" \
1789        -variable [itcl::scope _axisPopup(logscale)] -value 0 \
1790        -command [itcl::code $this SetAxis logscale]
1791    radiobutton $inner.log -text "logarithmic" \
1792        -variable [itcl::scope _axisPopup(logscale)] -value 1 \
1793        -command [itcl::code $this SetAxis logscale]
1794
1795    blt::table $inner \
1796        0,0 $inner.labell -anchor w \
1797        0,1 $inner.label -anchor w -fill x  -cspan 3 \
1798        1,0 $inner.formatl -anchor w \
1799        1,1 $inner.format -anchor w -fill x  -cspan 3 \
1800        2,0 $inner.scalel -anchor w \
1801        2,2 $inner.linear -anchor w \
1802        2,3 $inner.log -anchor w \
1803        3,0 $inner.rangel -anchor w \
1804        4,0 $inner.manual -anchor w -padx 4 \
1805        4,2 $inner.minl -anchor e \
1806        4,3 $inner.min -anchor w \
1807        5,2 $inner.maxl -anchor e \
1808        5,3 $inner.max -anchor w \
1809        6,0 $inner.auto -anchor w -padx 4 \
1810        6,2 $inner.tight -anchor w \
1811        6,3 $inner.loose -anchor w \
1812       
1813
1814    blt::table configure $inner r2 -pady 4
1815    blt::table configure $inner c1 -width 20
1816    update
1817}
1818
1819#
1820# ShowAxisPopup --
1821#
1822#       Displays the axis dialog for an axis.  It initializes the
1823#       _axisInfo variables for that axis if necessary.
1824#
1825itcl::body Rappture::XyResult::ShowAxisPopup { axis } {
1826    set g $itk_component(plot)
1827    set popup $itk_component(hull).axes
1828
1829    if { ![winfo exists $popup] } {
1830        BuildAxisPopup $popup
1831    }
1832    set _axisPopup(axis)     $axis
1833    set _axisPopup(label)    [$g axis cget $axis -title]
1834    set _axisPopup(logscale) [$g axis cget $axis -logscale]
1835    set _axisPopup(loose)    [$g axis cget $axis -loose]
1836    if { ![info exists _axisPopup($axis-format)] } {
1837        set inner [$itk_component(hull).axes component inner]
1838        set _axisPopup($axis-format) "%.6g"
1839        set fmts [$inner.format choices get -value]
1840        set i [lsearch -exact $fmts $_axisPopup($axis-format)]
1841        if {$i < 0} { set i 0 }  ;# use Auto choice
1842        $inner.format value [$inner.format choices get -label $i]
1843    }
1844    foreach {min max} [$g axis limits $axis] break
1845    if { $_axisPopup(logscale) } {
1846        set type "log"
1847    } else {
1848        set type "lin"
1849    }
1850    set amin ""
1851    if { [info exists _limits(${axis}${type}-min)] } {
1852        set amin $_limits(${axis}${type}-min)
1853    }
1854    set amax ""
1855    if { [info exists _limits(${axis}${type}-max)] } {
1856        set amax $_limits(${axis}${type}-max)
1857    }
1858    set auto 1
1859    if { $amin != "" || $amax != "" } {
1860        set auto 0
1861    }
1862    if { ![info exists _axisPopup($axis-auto)] } {
1863        set _axisPopup($axis-auto) $auto;# Defaults to automatic
1864    }
1865    set _axisPopup(auto)  $_axisPopup($axis-auto)
1866    SetAxisRangeState $axis
1867    if { ![info exists _axisPopup($axis-min)] } {
1868        if { $amin != "" } {
1869            set _axisPopup($axis-min) $amin
1870            set _axisPopup(min)   $_axisPopup($axis-min)
1871            SetAxis min
1872        } else {
1873            set _axisPopup($axis-min) $min
1874        }
1875    }
1876    if { ![info exists _axisPopup($axis-max)] } {
1877        if { $amax != "" } {
1878            set _axisPopup($axis-max) $amax
1879            set _axisPopup(max)   $_axisPopup($axis-max)
1880            SetAxis max
1881        } else {
1882            set _axisPopup($axis-max) $max
1883        }
1884    }
1885    set _axisPopup(min)   $_axisPopup($axis-min)
1886    set _axisPopup(max)   $_axisPopup($axis-max)
1887    set _axisPopup(axis) $axis
1888
1889    #
1890    # Figure out where the window should pop up.
1891    #
1892    set x [winfo rootx $g]
1893    set y [winfo rooty $g]
1894    set w [winfo width $g]
1895    set h [winfo height $g]
1896    foreach {x0 y0 pw ph} [$g extents plotarea] break
1897    switch -glob -- $axis {
1898        x {
1899            set x [expr {round($x + $x0+0.5*$pw)}]
1900            set y [expr {round($y + $y0+$ph + 0.5*($h-$y0-$ph))}]
1901            set dir "above"
1902        }
1903        x* {
1904            set x [expr {round($x + $x0+0.5*$pw)}]
1905            set dir "below"
1906            set allx [$itk_component(plot) x2axis use]
1907            set max [llength $allx]
1908            set i [lsearch -exact $allx $axis]
1909            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1910        }
1911        y {
1912            set x [expr {round($x + 0.5*$x0)}]
1913            set y [expr {round($y + $y0+0.5*$ph)}]
1914            set dir "right"
1915        }
1916        y* {
1917            set y [expr {round($y + $y0+0.5*$ph)}]
1918            set dir "left"
1919            set ally [$g y2axis use]
1920            set max [llength $ally]
1921            set i [lsearch -exact $ally $axis]
1922            set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1923            set x [expr {round($x+$x0+$pw + ($i+0.5)*($w-$x0-$pw)/double($max))}]
1924        }
1925    }
1926    $popup activate @$x,$y $dir
1927}
1928
1929#
1930# GetFormattedValue --
1931#
1932#       Callback routine for the axis format procedure.  It formats the
1933#       axis tick label according to the selected format.  This routine
1934#       is also used to format tooltip values.
1935#
1936itcl::body Rappture::XyResult::GetFormattedValue { axis g value } {
1937    if { [$g axis cget $axis -logscale] ||
1938         ![info exists _axisPopup($axis-format)] } {
1939        set fmt "%.6g"
1940    } else {
1941        set fmt $_axisPopup($axis-format)
1942    }
1943    return [format $fmt $value]
1944}
Note: See TracBrowser for help on using the repository browser.