source: branches/blt4/gui/scripts/xyresult.tcl @ 2969

Last change on this file since 2969 was 2969, checked in by gah, 12 years ago

datatable rows/columns numbered from 0 now. Index in resultviewer is really a list. Fixed element deactivate test (was always false)

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