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

Last change on this file since 1123 was 1123, checked in by gah, 16 years ago

add raise/lower of xy-graph elements

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