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

Last change on this file since 1562 was 1562, checked in by gah, 15 years ago

Added cartoon image, changed molecule representation controls, added print options, relabel duplicate xy elements

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