source: branches/1.2/gui/scripts/xyresult.tcl @ 3652

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

add KP_Enter to Return bindings. Implement OK handler in visviewer base class. Fix setting # of isolines while running a sequence of heightmaps/contours

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