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

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