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

Last change on this file since 1444 was 1420, checked in by gah, 15 years ago
File size: 45.5 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 {xydata}
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 xydata $args {
438        # find the axes for this curve (e.g., {x y2})
439        foreach {map(x) map(y)} [_getAxes $xydata] 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} [$xydata 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 {[$xydata 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 xydata [get] {
636        foreach ax {x y} {
637            set label [$xydata 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 [$xydata 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 xydata $_clist {
701        set label [$xydata hints label]
702        foreach {mapx mapy} [_getAxes $xydata] break
703
704        foreach comp [$xydata components] {
705            set xv [$xydata mesh $comp]
706            set yv [$xydata values $comp]
707
708            if {[info exists _curve2color($xydata)]} {
709                set color $_curve2color($xydata)
710            } else {
711                set color [$xydata hints color]
712                if {"" == $color} {
713                    set color black
714                }
715            }
716
717            if {[info exists _curve2width($xydata)]} {
718                set lwidth $_curve2width($xydata)
719            } else {
720                set lwidth 2
721            }
722
723            if {[info exists _curve2dashes($xydata)]} {
724                set dashes $_curve2dashes($xydata)
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) $xydata
739
740            $g element create $elem -x $xv -y $yv \
741                -symbol $sym -pixels $pixels -linewidth $lwidth -label $label \
742                -color $color -dashes $dashes \
743                -mapx $mapx -mapy $mapy
744        }
745    }
746
747    foreach xydata $_clist {
748        set xmin -Inf
749        set ymin -Inf
750        set xmax Inf
751        set ymax Inf
752        #
753        # Create text/line markers for each *axis.marker specified.
754        #
755        foreach m [$xydata xmarkers] {
756            foreach {at label style} $m break
757            set id [$g marker create line -coords [list $at $ymin $at $ymax]]
758            $g marker bind $id <Enter> \
759                [itcl::code $this _enterMarker $g x-$label $at $ymin $at]
760            $g marker bind $id <Leave> \
761                [itcl::code $this _leaveMarker $g x-$label]
762            set options [_getLineMarkerOptions $style]
763            if { $options != "" } {
764                eval $g marker configure $id $options
765            }
766            if { $label != "" } {
767                set id [$g marker create text -anchor nw \
768                            -text $label -coords [list $at $ymax]]
769                set options [_getTextMarkerOptions $style]
770                if { $options != "" } {
771                    eval $g marker configure $id $options
772                }
773            }
774        }
775        foreach m [$xydata ymarkers] {
776            foreach {at label style} $m break
777            set id [$g marker create line -coords [list $xmin $at $xmax $at]]
778            $g marker bind $id <Enter> \
779                [itcl::code $this _enterMarker $g y-$label $at $xmin $at]
780            $g marker bind $id <Leave> \
781                [itcl::code $this _leaveMarker $g y-$label]
782            set options [_getLineMarkerOptions $style]
783            if { $options != "" } {
784                eval $g marker configure $id $options
785            }
786            if { $label != "" } {
787                set id [$g marker create text -anchor se \
788                        -text $label -coords [list $xmax $at]]
789                set options [_getTextMarkerOptions $style]
790                if { $options != "" } {
791                    eval $g marker configure $id $options
792                }
793            }
794        }
795    }
796    $itk_component(legend) reset
797}
798
799# ----------------------------------------------------------------------
800# USAGE: _resetLimits
801#
802# Used internally to apply automatic limits to the axes for the
803# current plot.
804# ----------------------------------------------------------------------
805itcl::body Rappture::XyResult::_resetLimits {} {
806    set g $itk_component(plot)
807
808    #
809    # HACK ALERT!
810    # Use this code to fix up the y-axis limits for the BLT graph.
811    # The auto-limits don't always work well.  We want them to be
812    # set to a "nice" number slightly above or below the min/max
813    # limits.
814    #
815    foreach axis [$g axis names] {
816        if {[info exists _limits(${axis}lin-min)]} {
817            set log [$g axis cget $axis -logscale]
818            if {$log} {
819                set min $_limits(${axis}log-min)
820                if {$min == 0} { set min 1 }
821                set max $_limits(${axis}log-max)
822                if {$max == 0} { set max 1 }
823
824                if {$min == $max} {
825                    set logmin [expr {floor(log10(abs(0.9*$min)))}]
826                    set logmax [expr {ceil(log10(abs(1.1*$max)))}]
827                } else {
828                    set logmin [expr {floor(log10(abs($min)))}]
829                    set logmax [expr {ceil(log10(abs($max)))}]
830                    if {[string match y* $axis]} {
831                        # add a little padding
832                        set delta [expr {$logmax-$logmin}]
833                        if {$delta == 0} { set delta 1 }
834                        set logmin [expr {$logmin-0.05*$delta}]
835                        set logmax [expr {$logmax+0.05*$delta}]
836                    }
837                }
838                if {$logmin < -300} {
839                    set min 1e-300
840                } elseif {$logmin > 300} {
841                    set min 1e+300
842                } else {
843                    set min [expr {pow(10.0,$logmin)}]
844                }
845
846                if {$logmax < -300} {
847                    set max 1e-300
848                } elseif {$logmax > 300} {
849                    set max 1e+300
850                } else {
851                    set max [expr {pow(10.0,$logmax)}]
852                }
853            } else {
854                set min $_limits(${axis}lin-min)
855                set max $_limits(${axis}lin-max)
856
857                if {[string match y* $axis]} {
858                    # add a little padding
859                    set delta [expr {$max-$min}]
860                    set min [expr {$min-0.05*$delta}]
861                    set max [expr {$max+0.05*$delta}]
862                }
863            }
864            if {$min < $max} {
865                $g axis configure $axis -min $min -max $max
866            } else {
867                $g axis configure $axis -min "" -max ""
868            }
869        } else {
870            $g axis configure $axis -min "" -max ""
871        }
872    }
873}
874
875# ----------------------------------------------------------------------
876# USAGE: _zoom reset
877#
878# Called automatically when the user clicks on one of the zoom
879# controls for this widget.  Changes the zoom for the current view.
880# ----------------------------------------------------------------------
881itcl::body Rappture::XyResult::_zoom {option args} {
882    switch -- $option {
883        reset {
884            _resetLimits
885        }
886    }
887}
888
889# ----------------------------------------------------------------------
890# USAGE: _hilite <state> <x> <y>
891#
892# Called automatically when the user brushes one of the elements
893# on the plot.  Causes the element to highlight and a tooltip to
894# pop up with element info.
895# ----------------------------------------------------------------------
896itcl::body Rappture::XyResult::_hilite {state x y} {
897    set g $itk_component(plot)
898    set elem ""
899 
900    # Peek inside of Blt_ZoomStack package to see if we're currently in the
901    # middle of a zoom selection.
902    if {[info exists ::zoomInfo($g,corner)] && $::zoomInfo($g,corner) == "B" } {
903        return;
904    }
905    set tip ""
906    if {$state == "at"} {
907        if {[$g element closest $x $y info -interpolate yes]} {
908            # for dealing with xy line plots
909            set elem $info(name)
910
911            # Some elements are generated dynamically and therefore will
912            # not have a curve object associated with them.
913            set mapx [$g element cget $elem -mapx]
914            set mapy [$g element cget $elem -mapy]
915            if {[info exists _elem2curve($elem)]} {
916                foreach {mapx mapy} [_getAxes $_elem2curve($elem)] break
917            }
918
919            # search again for an exact point -- this time don't interpolate
920            set tip ""
921            array unset info
922            if {[$g element closest $x $y info -interpolate no]
923                  && $info(name) == $elem} {
924
925                set x [$g axis transform $mapx $info(x)]
926                set y [$g axis transform $mapy $info(y)]
927               
928                if {[info exists _elem2curve($elem)]} {
929                    set curve $_elem2curve($elem)
930                    set yunits [$curve hints yunits]
931                    set xunits [$curve hints xunits]
932                } else {
933                    set xunits ""
934                    set yunits ""
935                }
936                set tip [$g element cget $elem -label]
937                set yval [_axis format y dummy $info(y)]
938                append tip "\n$yval$yunits"
939                set xval [_axis format x dummy $info(x)]
940                append tip " @ $xval$xunits"
941                set tip [string trim $tip]
942            }
943            set state 1
944        } elseif {[$g element closest $x $y info -interpolate no]} {
945            # for dealing with xy scatter plot
946            set elem $info(name)
947
948            # Some elements are generated dynamically and therefore will
949            # not have a curve object associated with them.
950            set mapx [$g element cget $elem -mapx]
951            set mapy [$g element cget $elem -mapy]
952            if {[info exists _elem2curve($elem)]} {
953                foreach {mapx mapy} [_getAxes $_elem2curve($elem)] break
954            }
955
956            set tip ""
957            set x [$g axis transform $mapx $info(x)]
958            set y [$g axis transform $mapy $info(y)]
959               
960            if {[info exists _elem2curve($elem)]} {
961                set curve $_elem2curve($elem)
962                set yunits [$curve hints yunits]
963                set xunits [$curve hints xunits]
964            } else {
965                set xunits ""
966                set yunits ""
967            }
968            set tip [$g element cget $elem -label]
969            set yval [_axis format y dummy $info(y)]
970            append tip "\n$yval$yunits"
971            set xval [_axis format x dummy $info(x)]
972            append tip " @ $xval$xunits"
973            set tip [string trim $tip]
974            set state 1
975        } else {
976            set state 0
977        }
978    }
979
980    if {$state} {
981        #
982        # Highlight ON:
983        # - activate trace
984        # - multiple axes? dim other axes
985        # - pop up tooltip about data
986        #
987        if { [$g element exists $_hilite(elem)] && $_hilite(elem) != $elem } {
988            $g element deactivate $_hilite(elem)
989            $g crosshairs configure -hide yes
990            Rappture::Tooltip::tooltip cancel
991        }
992        $g element activate $elem
993        set _hilite(elem) $elem
994
995        set mapx [$g element cget $elem -mapx]
996        set mapy [$g element cget $elem -mapy]
997        if {[info exists _elem2curve($elem)]} {
998            foreach {mapx mapy} [_getAxes $_elem2curve($elem)] break
999        }
1000        set allx [$g x2axis use]
1001        if {[llength $allx] > 0} {
1002            lappend allx x  ;# fix main x-axis too
1003            foreach axis $allx {
1004                if {$axis == $mapx} {
1005                    $g axis configure $axis -color $itk_option(-foreground) \
1006                        -titlecolor $itk_option(-foreground)
1007                } else {
1008                    $g axis configure $axis -color $itk_option(-dimcolor) \
1009                        -titlecolor $itk_option(-dimcolor)
1010                }
1011            }
1012        }
1013        set ally [$g y2axis use]
1014        if {[llength $ally] > 0} {
1015            lappend ally y  ;# fix main y-axis too
1016            foreach axis $ally {
1017                if {$axis == $mapy} {
1018                    $g axis configure $axis -color $itk_option(-foreground) \
1019                        -titlecolor $itk_option(-foreground)
1020                } else {
1021                    $g axis configure $axis -color $itk_option(-dimcolor) \
1022                        -titlecolor $itk_option(-dimcolor)
1023                }
1024            }
1025        }
1026
1027        if {"" != $tip} {
1028            $g crosshairs configure -hide no -position @$x,$y
1029
1030            if {$x > 0.5*[winfo width $g]} {
1031                if {$x < 4} {
1032                    set tipx "-0"
1033                } else {
1034                    set tipx "-[expr {$x-4}]"  ;# move tooltip to the left
1035                }
1036            } else {
1037                if {$x < -4} {
1038                    set tipx "+0"
1039                } else {
1040                    set tipx "+[expr {$x+4}]"  ;# move tooltip to the right
1041                }
1042            }
1043            if {$y > 0.5*[winfo height $g]} {
1044                if {$y < 4} {
1045                    set tipy "-0"
1046                } else {
1047                    set tipy "-[expr {$y-4}]"  ;# move tooltip to the top
1048                }
1049            } else {
1050                if {$y < -4} {
1051                    set tipy "+0"
1052                } else {
1053                    set tipy "+[expr {$y+4}]"  ;# move tooltip to the bottom
1054                }
1055            }
1056            Rappture::Tooltip::text $g $tip
1057            Rappture::Tooltip::tooltip show $g $tipx,$tipy
1058        }
1059    } else {
1060        #
1061        # Highlight OFF:
1062        # - deactivate (color back to normal)
1063        # - put all axes back to normal color
1064        # - take down tooltip
1065        #
1066        if { [$g element exists $_hilite(elem)] } {
1067            $g element deactivate $_hilite(elem)
1068        }
1069        set allx [$g x2axis use]
1070        if {[llength $allx] > 0} {
1071            lappend allx x  ;# fix main x-axis too
1072            foreach axis $allx {
1073                $g axis configure $axis -color $itk_option(-foreground) \
1074                    -titlecolor $itk_option(-foreground)
1075            }
1076        }
1077       
1078        set ally [$g y2axis use]
1079        if {[llength $ally] > 0} {
1080            lappend ally y  ;# fix main y-axis too
1081            foreach axis $ally {
1082                $g axis configure $axis -color $itk_option(-foreground) \
1083                    -titlecolor $itk_option(-foreground)
1084            }
1085        }
1086
1087        $g crosshairs configure -hide yes
1088
1089        # only cancel in plotting area or we'll mess up axes
1090        if {[$g inside $x $y]} {
1091            Rappture::Tooltip::tooltip cancel
1092        }
1093
1094        # There is no currently highlighted element
1095        set _hilite(elem) ""
1096    }
1097}
1098
1099# ----------------------------------------------------------------------
1100# USAGE: _axis hilite <axis> <state>
1101#
1102# USAGE: _axis click <axis> <x> <y>
1103# USAGE: _axis drag <axis> <x> <y>
1104# USAGE: _axis release <axis> <x> <y>
1105#
1106# USAGE: _axis edit <axis>
1107# USAGE: _axis changed <axis> <what>
1108# USAGE: _axis format <axis> <widget> <value>
1109# USAGE: _axis scale <axis> linear|log
1110#
1111# Used internally to handle editing of the x/y axes.  The hilite
1112# operation causes the axis to light up.  The edit operation pops
1113# up a panel with editing options.  The changed operation applies
1114# changes from the panel.
1115# ----------------------------------------------------------------------
1116itcl::body Rappture::XyResult::_axis {option args} {
1117    set inner [$itk_component(hull).axes component inner]
1118
1119    switch -- $option {
1120        hilite {
1121            if {[llength $args] != 2} {
1122                error "wrong # args: should be \"_axis hilite axis state\""
1123            }
1124            set g $itk_component(plot)
1125            set axis [lindex $args 0]
1126            set state [lindex $args 1]
1127
1128            if {$state} {
1129                $g axis configure $axis \
1130                    -color $itk_option(-activecolor) \
1131                    -titlecolor $itk_option(-activecolor)
1132
1133                set x [expr {[winfo pointerx $g]+4}]
1134                set y [expr {[winfo pointery $g]+4}]
1135                Rappture::Tooltip::tooltip pending $g-$axis @$x,$y
1136            } else {
1137                $g axis configure $axis \
1138                    -color $itk_option(-foreground) \
1139                    -titlecolor $itk_option(-foreground)
1140                Rappture::Tooltip::tooltip cancel
1141            }
1142        }
1143        click {
1144            if {[llength $args] != 3} {
1145                error "wrong # args: should be \"_axis click axis x y\""
1146            }
1147            set axis [lindex $args 0]
1148            set x [lindex $args 1]
1149            set y [lindex $args 2]
1150            set g $itk_component(plot)
1151
1152            set _axis(moved) 0
1153            set _axis(click-x) $x
1154            set _axis(click-y) $y
1155            foreach {min max} [$g axis limits $axis] break
1156            set _axis(min0) $min
1157            set _axis(max0) $max
1158            Rappture::Tooltip::tooltip cancel
1159        }
1160        drag {
1161            if {[llength $args] != 3} {
1162                error "wrong # args: should be \"_axis drag axis x y\""
1163            }
1164            if {![info exists _axis(moved)]} {
1165                return  ;# must have skipped click event -- ignore
1166            }
1167            set axis [lindex $args 0]
1168            set x [lindex $args 1]
1169            set y [lindex $args 2]
1170            set g $itk_component(plot)
1171
1172            if {[info exists _axis(click-x)] && [info exists _axis(click-y)]} {
1173                foreach {x0 y0 pw ph} [$g extents plotarea] break
1174                switch -glob $axis {
1175                  x* {
1176                    set pix $x
1177                    set pix0 $_axis(click-x)
1178                    set pixmin $x0
1179                    set pixmax [expr {$x0+$pw}]
1180                  }
1181                  y* {
1182                    set pix $y
1183                    set pix0 $_axis(click-y)
1184                    set pixmin [expr {$y0+$ph}]
1185                    set pixmax $y0
1186                  }
1187                }
1188                set log [$g axis cget $axis -logscale]
1189                set min $_axis(min0)
1190                set max $_axis(max0)
1191                set dpix [expr {abs($pix-$pix0)}]
1192                set v0 [$g axis invtransform $axis $pixmin]
1193                set v1 [$g axis invtransform $axis [expr {$pixmin+$dpix}]]
1194                if {$log} {
1195                    set v0 [expr {log10($v0)}]
1196                    set v1 [expr {log10($v1)}]
1197                    set min [expr {log10($min)}]
1198                    set max [expr {log10($max)}]
1199                }
1200
1201                if {$pix > $pix0} {
1202                    set delta [expr {$v1-$v0}]
1203                } else {
1204                    set delta [expr {$v0-$v1}]
1205                }
1206                set min [expr {$min-$delta}]
1207                set max [expr {$max-$delta}]
1208                if {$log} {
1209                    set min [expr {pow(10.0,$min)}]
1210                    set max [expr {pow(10.0,$max)}]
1211                }
1212                $g axis configure $axis -min $min -max $max
1213
1214                # move axis, don't edit on release
1215                set _axis(move) 1
1216            }
1217        }
1218        release {
1219            if {[llength $args] != 3} {
1220                error "wrong # args: should be \"_axis release axis x y\""
1221            }
1222            if {![info exists _axis(moved)]} {
1223                return  ;# must have skipped click event -- ignore
1224            }
1225            set axis [lindex $args 0]
1226            set x [lindex $args 1]
1227            set y [lindex $args 2]
1228
1229            if {!$_axis(moved)} {
1230                # small movement? then treat as click -- pop up axis editor
1231                set dx [expr {abs($x-$_axis(click-x))}]
1232                set dy [expr {abs($y-$_axis(click-y))}]
1233                if {$dx < 2 && $dy < 2} {
1234                    _axis edit $axis
1235                }
1236            } else {
1237                # one last movement
1238                _axis drag $axis $x $y
1239            }
1240            catch {unset _axis}
1241        }
1242        edit {
1243            if {[llength $args] != 1} {
1244                error "wrong # args: should be \"_axis edit axis\""
1245            }
1246            set axis [lindex $args 0]
1247            set _axisPopup(current) $axis
1248
1249            # apply last value when deactivating
1250            $itk_component(hull).axes configure -deactivatecommand \
1251                [itcl::code $this _axis changed $axis focus]
1252
1253            # fix axis label controls...
1254            set label [$itk_component(plot) axis cget $axis -title]
1255            $inner.label delete 0 end
1256            $inner.label insert end $label
1257            bind $inner.label <KeyPress-Return> \
1258                [itcl::code $this _axis changed $axis label]
1259            bind $inner.label <FocusOut> \
1260                [itcl::code $this _axis changed $axis label]
1261
1262            # fix min/max controls...
1263            foreach {min max} [$itk_component(plot) axis limits $axis] break
1264            $inner.min delete 0 end
1265            $inner.min insert end $min
1266            bind $inner.min <KeyPress-Return> \
1267                [itcl::code $this _axis changed $axis min]
1268            bind $inner.min <FocusOut> \
1269                [itcl::code $this _axis changed $axis min]
1270
1271            $inner.max delete 0 end
1272            $inner.max insert end $max
1273            bind $inner.max <KeyPress-Return> \
1274                [itcl::code $this _axis changed $axis max]
1275            bind $inner.max <FocusOut> \
1276                [itcl::code $this _axis changed $axis max]
1277
1278            # fix format control...
1279            set fmts [$inner.format choices get -value]
1280            set i [lsearch -exact $fmts $_axisPopup(format-$axis)]
1281            if {$i < 0} { set i 0 }  ;# use Auto choice
1282            $inner.format value [$inner.format choices get -label $i]
1283
1284            bind $inner.format <<Value>> \
1285                [itcl::code $this _axis changed $axis format]
1286
1287            # fix scale control...
1288            if {[$itk_component(plot) axis cget $axis -logscale]} {
1289                set _axisPopup(scale) "log"
1290                $inner.format configure -state disabled
1291            } else {
1292                set _axisPopup(scale) "linear"
1293                $inner.format configure -state normal
1294            }
1295            $inner.scales.linear configure \
1296                -command [itcl::code $this _axis changed $axis scale]
1297            $inner.scales.log configure \
1298                -command [itcl::code $this _axis changed $axis scale]
1299
1300            #
1301            # Figure out where the window should pop up.
1302            #
1303            set x [winfo rootx $itk_component(plot)]
1304            set y [winfo rooty $itk_component(plot)]
1305            set w [winfo width $itk_component(plot)]
1306            set h [winfo height $itk_component(plot)]
1307            foreach {x0 y0 pw ph} [$itk_component(plot) extents plotarea] break
1308            switch -glob -- $axis {
1309                x {
1310                    set x [expr {round($x + $x0+0.5*$pw)}]
1311                    set y [expr {round($y + $y0+$ph + 0.5*($h-$y0-$ph))}]
1312                    set dir "above"
1313                }
1314                x* {
1315                    set x [expr {round($x + $x0+0.5*$pw)}]
1316                    set dir "below"
1317                    set allx [$itk_component(plot) x2axis use]
1318                    set max [llength $allx]
1319                    set i [lsearch -exact $allx $axis]
1320                    set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1321                }
1322                y {
1323                    set x [expr {round($x + 0.5*$x0)}]
1324                    set y [expr {round($y + $y0+0.5*$ph)}]
1325                    set dir "right"
1326                }
1327                y* {
1328                    set y [expr {round($y + $y0+0.5*$ph)}]
1329                    set dir "left"
1330                    set ally [$itk_component(plot) y2axis use]
1331                    set max [llength $ally]
1332                    set i [lsearch -exact $ally $axis]
1333                    set y [expr {round($y + ($i+0.5)*$y0/double($max))}]
1334                    set x [expr {round($x+$x0+$pw + ($i+0.5)*($w-$x0-$pw)/double($max))}]
1335                }
1336            }
1337            $itk_component(hull).axes activate @$x,$y $dir
1338        }
1339        changed {
1340            if {[llength $args] != 2} {
1341                error "wrong # args: should be \"_axis changed axis what\""
1342            }
1343            set axis [lindex $args 0]
1344            set what [lindex $args 1]
1345            if {$what == "focus"} {
1346                set what [focus]
1347                if {[winfo exists $what]} {
1348                    set what [winfo name $what]
1349                }
1350            }
1351
1352            switch -- $what {
1353                label {
1354                    set val [$inner.label get]
1355                    $itk_component(plot) axis configure $axis -title $val
1356                }
1357                min {
1358                    set val [$inner.min get]
1359                    if {![string is double -strict $val]} {
1360                        Rappture::Tooltip::cue $inner.min "Must be a number"
1361                        bell
1362                        return
1363                    }
1364
1365                    set max [lindex [$itk_component(plot) axis limits $axis] 1]
1366                    if {$val >= $max} {
1367                        Rappture::Tooltip::cue $inner.min "Must be <= max ($max)"
1368                        bell
1369                        return
1370                    }
1371                    catch {
1372                        # can fail in log mode
1373                        $itk_component(plot) axis configure $axis -min $val
1374                    }
1375                    foreach {min max} [$itk_component(plot) axis limits $axis] break
1376                    $inner.min delete 0 end
1377                    $inner.min insert end $min
1378                }
1379                max {
1380                    set val [$inner.max get]
1381                    if {![string is double -strict $val]} {
1382                        Rappture::Tooltip::cue $inner.max "Should be a number"
1383                        bell
1384                        return
1385                    }
1386
1387                    set min [lindex [$itk_component(plot) axis limits $axis] 0]
1388                    if {$val <= $min} {
1389                        Rappture::Tooltip::cue $inner.max "Must be >= min ($min)"
1390                        bell
1391                        return
1392                    }
1393                    catch {
1394                        # can fail in log mode
1395                        $itk_component(plot) axis configure $axis -max $val
1396                    }
1397                    foreach {min max} [$itk_component(plot) axis limits $axis] break
1398                    $inner.max delete 0 end
1399                    $inner.max insert end $max
1400                }
1401                format {
1402                    set fmt [$inner.format translate [$inner.format value]]
1403                    set _axisPopup(format-$axis) $fmt
1404
1405                    # force a refresh
1406                    $itk_component(plot) axis configure $axis -min \
1407                        [$itk_component(plot) axis cget $axis -min]
1408                }
1409                scale {
1410                    _axis scale $axis $_axisPopup(scale)
1411
1412                    if {$_axisPopup(scale) == "log"} {
1413                        $inner.format configure -state disabled
1414                    } else {
1415                        $inner.format configure -state normal
1416                    }
1417
1418                    foreach {min max} [$itk_component(plot) axis limits $axis] break
1419                    $inner.min delete 0 end
1420                    $inner.min insert end $min
1421                    $inner.max delete 0 end
1422                    $inner.max insert end $max
1423                }
1424                default {
1425                    # be lenient so we can handle the "focus" case
1426                }
1427            }
1428        }
1429        format {
1430            if {[llength $args] != 3} {
1431                error "wrong # args: should be \"_axis format axis widget value\""
1432            }
1433            set axis [lindex $args 0]
1434            set value [lindex $args 2]
1435
1436            if {[$itk_component(plot) axis cget $axis -logscale]} {
1437                set fmt "%.3g"
1438            } else {
1439                set fmt $_axisPopup(format-$axis)
1440            }
1441            return [format $fmt $value]
1442        }
1443        scale {
1444            if {[llength $args] != 2} {
1445                error "wrong # args: should be \"_axis scale axis type\""
1446            }
1447            set axis [lindex $args 0]
1448            set type [lindex $args 1]
1449
1450            if {$type == "log"} {
1451                catch {$itk_component(plot) axis configure $axis -logscale 1}
1452                # leave format alone in log mode
1453                $itk_component(plot) axis configure $axis -command ""
1454            } else {
1455                catch {$itk_component(plot) axis configure $axis -logscale 0}
1456                # use special formatting for linear mode
1457                $itk_component(plot) axis configure $axis -command \
1458                    [itcl::code $this _axis format $axis]
1459            }
1460        }
1461        default {
1462            error "bad option \"$option\": should be changed, edit, hilite, or format"
1463        }
1464    }
1465}
1466
1467
1468# ----------------------------------------------------------------------
1469# USAGE: _getLineMarkerOptions <style>
1470#
1471# Used internally to create a list of configuration options specific to the
1472# axis line marker.  The input is a list of name value pairs.  Options that
1473# are not recognized are ignored.
1474# ----------------------------------------------------------------------
1475itcl::body Rappture::XyResult::_getLineMarkerOptions {style} {
1476    array set lineOptions {
1477        "-color"  "-outline"
1478        "-dashes" "-dashes"
1479        "-linecolor" "-outline"
1480        "-linewidth" "-linewidth"
1481    }
1482    set options {}
1483    foreach {name value} $style {
1484        if { [info exists lineOptions($name)] } {
1485            lappend options $lineOptions($name) $value
1486        }
1487    }
1488    return $options
1489}
1490
1491# ----------------------------------------------------------------------
1492# USAGE: _getTextMarkerOptions <style>
1493#
1494# Used internally to create a list of configuration options specific to the
1495# axis text marker.  The input is a list of name value pairs.  Options that
1496# are not recognized are ignored.
1497# ----------------------------------------------------------------------
1498itcl::body Rappture::XyResult::_getTextMarkerOptions {style} {
1499    array set textOptions {
1500        "-color"  "-outline"
1501        "-textcolor"  "-outline"
1502        "-font"   "-font"
1503        "-xoffset" "-xoffset"
1504        "-yoffset" "-yoffset"
1505        "-anchor" "-anchor"
1506    }
1507    set options {}
1508    foreach {name value} $style {
1509        if { [info exists textOptions($name)] } {
1510            lappend options $textOptions($name) $value
1511        }
1512    }
1513    return $options
1514}
1515
1516# ----------------------------------------------------------------------
1517# USAGE: _getAxes <curveObj>
1518#
1519# Used internally to figure out the axes used to plot the given
1520# <curveObj>.  Returns a list of the form {x y}, where x is the
1521# x-axis name (x, x2, x3, etc.), and y is the y-axis name.
1522# ----------------------------------------------------------------------
1523itcl::body Rappture::XyResult::_getAxes {xydata} {
1524    # rebuild if needed, so we know about the axes
1525    if {[$_dispatcher ispending !rebuild]} {
1526        $_dispatcher cancel !rebuild
1527        $_dispatcher event -now !rebuild
1528    }
1529
1530    # what is the x axis?  x? x2? x3? ...
1531    set xlabel [$xydata hints xlabel]
1532    if {[info exists _label2axis(x-$xlabel)]} {
1533        set mapx $_label2axis(x-$xlabel)
1534    } else {
1535        set mapx "x"
1536    }
1537
1538    # what is the y axis?  y? y2? y3? ...
1539    set ylabel [$xydata hints ylabel]
1540    if {[info exists _label2axis(y-$ylabel)]} {
1541        set mapy $_label2axis(y-$ylabel)
1542    } else {
1543        set mapy "y"
1544    }
1545
1546    return [list $mapx $mapy]
1547}
1548
1549# ----------------------------------------------------------------------
1550# CONFIGURATION OPTION: -gridcolor
1551# ----------------------------------------------------------------------
1552itcl::configbody Rappture::XyResult::gridcolor {
1553    if {"" == $itk_option(-gridcolor)} {
1554        $itk_component(plot) grid off
1555    } else {
1556        $itk_component(plot) grid configure -color $itk_option(-gridcolor)
1557        $itk_component(plot) grid on
1558    }
1559}
1560
1561# ----------------------------------------------------------------------
1562# CONFIGURATION OPTION: -autocolors
1563# ----------------------------------------------------------------------
1564itcl::configbody Rappture::XyResult::autocolors {
1565    foreach c $itk_option(-autocolors) {
1566        if {[catch {winfo rgb $itk_component(hull) $c}]} {
1567            error "bad color \"$c\""
1568        }
1569    }
1570    if {$_autoColorI >= [llength $itk_option(-autocolors)]} {
1571        set _autoColorI 0
1572    }
1573}
1574
1575itcl::body Rappture::XyResult::_enterMarker { g name x y text } {
1576    _leaveMarker $g $name
1577    set id [$g marker create text \
1578                -coords [list $x $y] \
1579                -yoffset -1 \
1580                -anchor s \
1581                -text $text]
1582    set _markers($name) $id
1583}
1584
1585itcl::body Rappture::XyResult::_leaveMarker { g name } {
1586    if { [info exists _markers($name)] } {
1587        set id $_markers($name)
1588        $g marker delete $id
1589        unset _markers($name)
1590    }
1591}
Note: See TracBrowser for help on using the repository browser.