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

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