source: trunk/gui/scripts/resultviewer.tcl @ 4045

Last change on this file since 4045 was 3803, checked in by gah, 11 years ago

fix limits on axis

File size: 20.8 KB
RevLine 
[3330]1# -*- mode: tcl; indent-tabs-mode: nil -*-
[11]2# ----------------------------------------------------------------------
3#  COMPONENT: ResultViewer - plots a collection of related results
4#
5#  This widget plots a collection of results that all represent
6#  the same quantity, but for various ranges of input values.  It
7#  is normally used as part of an Analyzer, to plot the various
8#  results selected by a ResultSet.
9# ======================================================================
10#  AUTHOR:  Michael McLennan, Purdue University
[3177]11#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
[115]12#
13#  See the file "license.terms" for information on usage and
14#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
[11]15# ======================================================================
16package require Itk
17
18itcl::class Rappture::ResultViewer {
19    inherit itk::Widget
20
[22]21    itk_option define -width width Width 4i
22    itk_option define -height height Height 4i
[11]23    itk_option define -colors colors Colors ""
24    itk_option define -clearcommand clearCommand ClearCommand ""
25    itk_option define -simulatecommand simulateCommand SimulateCommand ""
26
[3330]27    constructor {args} {
28        # defined below
29    }
30    destructor {
31        # defined below
32    }
[13]33    public method add {index xmlobj path}
34    public method clear {{index ""}}
[22]35    public method value {xmlobj}
[11]36
37    public method plot {option args}
[464]38    public method download {option args}
[11]39
40    protected method _plotAdd {xmlobj {settings ""}}
41    protected method _fixScale {args}
[2943]42    protected method _xml2data {xmlobj path}
43    protected method _cleanIndex {index}
[11]44
45    private variable _dispatcher ""  ;# dispatchers for !events
46    private variable _mode ""        ;# current plotting mode (xy, etc.)
47    private variable _mode2widget    ;# maps plotting mode => widget
[13]48    private variable _dataslots ""   ;# list of all data objects in this widget
[2943]49    private variable _xml2data       ;# maps xmlobj => data obj in _dataslots
[11]50}
[997]51
[11]52itk::usual ResultViewer {
53    keep -background -foreground -cursor -font
54}
55
56# ----------------------------------------------------------------------
57# CONSTRUCTOR
58# ----------------------------------------------------------------------
59itcl::body Rappture::ResultViewer::constructor {args} {
60    # create a dispatcher for events
61    Rappture::dispatcher _dispatcher
62    $_dispatcher register !scale
63    $_dispatcher dispatch $this !scale \
[1587]64        [itcl::code $this _fixScale]
[11]65
66    eval itk_initialize $args
67}
68
69# ----------------------------------------------------------------------
70# DESTRUCTOR
71# ----------------------------------------------------------------------
72itcl::body Rappture::ResultViewer::destructor {} {
[13]73    foreach slot $_dataslots {
[1587]74        foreach obj $slot {
75            itcl::delete object $obj
76        }
[11]77    }
78}
79
80# ----------------------------------------------------------------------
[13]81# USAGE: add <index> <xmlobj> <path>
[11]82#
[13]83# Adds a new result to this result viewer at the specified <index>.
84# Data is taken from the <xmlobj> object at the <path>.
[11]85# ----------------------------------------------------------------------
[13]86itcl::body Rappture::ResultViewer::add {index xmlobj path} {
[2943]87    set index [_cleanIndex $index]
[22]88    set dobj [_xml2data $xmlobj $path]
[11]89
[13]90    #
91    # If the index doesn't exist, then fill in empty slots and
92    # make it exist.
93    #
94    for {set i [llength $_dataslots]} {$i <= $index} {incr i} {
[1587]95        lappend _dataslots ""
[11]96    }
[13]97    set slot [lindex $_dataslots $index]
98    lappend slot $dobj
99    set _dataslots [lreplace $_dataslots $index $index $slot]
[11]100
101    $_dispatcher event -idle !scale
102}
103
104# ----------------------------------------------------------------------
[2943]105# USAGE: clear ?<index>|<xmlobj>?
[11]106#
[2943]107# Clears one or all results in this result viewer.  If a particular
108# <index> is specified, then all data objects at that index are
109# deleted.  If a particular <xmlobj> is specified, then all data
110# objects related to that <xmlobj> are removed--regardless of whether
111# they reside at one or more indices.
[11]112# ----------------------------------------------------------------------
[13]113itcl::body Rappture::ResultViewer::clear {{index ""}} {
[2943]114    if {$index ne ""} {
[1587]115        # clear one result
[2943]116        if {[catch {_cleanIndex $index} i] == 0} {
117            if {$i >= 0 && $i < [llength $_dataslots]} {
118                set slot [lindex $_dataslots $i]
119                foreach dobj $slot {
[3803]120                    if {"" != $_mode} {
121                        $_mode2widget($_mode) delete $dobj
122                    }
[2943]123                    itcl::delete object $dobj
124                }
125                set _dataslots [lreplace $_dataslots $i $i ""]
126                $_dispatcher event -idle !scale
127            }
128        } else {
129            foreach key [array names _xml2data $index-*] {
130                set dobj $_xml2data($key)
131
132                # search for and remove all references to this data object
133                for {set n 0} {$n < [llength $_dataslots]} {incr n} {
134                    set slot [lindex $_dataslots $n]
135                    set pos [lsearch -exact $slot $dobj]
136                    if {$pos >= 0} {
137                        set slot [lreplace $slot $pos $pos]
138                        set _dataslots [lreplace $_dataslots $n $n $slot]
139                        $_dispatcher event -idle !scale
140                    }
141                }
142
[3803]143                if {"" != $_mode} {
144                    $_mode2widget($_mode) delete $dobj
145                }
[2943]146                # destroy the object and forget it
[1587]147                itcl::delete object $dobj
[2943]148                unset _xml2data($key)
[1587]149            }
150        }
[13]151    } else {
[1587]152        # clear all results
153        plot clear
154        foreach slot $_dataslots {
155            foreach dobj $slot {
156                itcl::delete object $dobj
157            }
158        }
159        set _dataslots ""
[2943]160        catch {unset _xml2data}
[11]161    }
162}
163
164# ----------------------------------------------------------------------
[22]165# USAGE: value <xmlobj>
166#
167# Convenience method for showing a single value.  Loads the value
168# into the widget via add/clear, then immediately plots the value.
169# This makes the widget consistent with other widgets, such as
170# the DeviceEditor, etc.
171# ----------------------------------------------------------------------
172itcl::body Rappture::ResultViewer::value {xmlobj} {
173    clear
174    if {"" != $xmlobj} {
[1587]175        add 0 $xmlobj ""
176        plot add 0 ""
[22]177    }
178}
179
180# ----------------------------------------------------------------------
[2943]181# USAGE: plot add ?<simnum> <settings> <simnum> <settings> ...?
[11]182# USAGE: plot clear
183#
184# Used to manipulate the contents of this viewer.  The "plot clear"
185# command clears the current viewer.  Data is still stored in the
186# widget, but the results are not shown on screen.  The "plot add"
[2943]187# command adds the data at the specified <simnum> to the plot.  Each
188# <simnum> is the simulation number, like "#1", "#2", "#3", etc.  If
[11]189# the optional <settings> are specified, then they are applied
190# to the plot; otherwise, default settings are used.
191# ----------------------------------------------------------------------
192itcl::body Rappture::ResultViewer::plot {option args} {
193    switch -- $option {
[1587]194        add {
195            set params ""
196            foreach {index opts} $args {
197                if {$index == "params"} {
198                    set params $opts
199                    continue
200                }
[2943]201
202                set index [_cleanIndex $index]
[3799]203                lappend opts "-simulation" [expr $index + 1]
[1587]204                set reset "-color autoreset"
205                set slot [lindex $_dataslots $index]
206                foreach dobj $slot {
207                    set settings ""
208                    # start with color reset, only for first object in series
209                    if {"" != $reset} {
210                        set settings $reset
211                        set reset ""
212                    }
213                    # add default settings from data object
214                    if {[catch {$dobj hints style} style] == 0} {
215                        eval lappend settings $style
216                    }
217                    if {[catch {$dobj hints type} type] == 0} {
218                        if {"" != $type} {
219                            eval lappend settings "-type $type"
220                        }
221                    }
222                    # add override settings passed in here
223                    eval lappend settings $opts
224                    _plotAdd $dobj $settings
225                }
226            }
227            if {"" != $params && "" != $_mode} {
228                eval $_mode2widget($_mode) parameters $params
229            }
230        }
231        clear {
232            # clear the contents of the current mode
233            if {"" != $_mode} {
234                $_mode2widget($_mode) delete
235            }
236        }
237        default {
238            error "bad option \"$option\": should be add or clear"
239        }
[11]240    }
241}
242
243# ----------------------------------------------------------------------
244# USAGE: _plotAdd <dataobj> <settings>
245#
246# Used internally to add a <dataobj> representing some data to
247# the plot at the top of this widget.  The data is added to the
248# current plot.  Use the "clear" function to clear before adding
249# new data.
250# ----------------------------------------------------------------------
251itcl::body Rappture::ResultViewer::_plotAdd {dataobj {settings ""}} {
252    switch -- [$dataobj info class] {
[1930]253        ::Rappture::DataTable {
254            set mode "datatable"
255            if {![info exists _mode2widget($mode)]} {
256                set w $itk_interior.datatable
257                Rappture::DataTableResult $w
258                set _mode2widget($mode) $w
259            }
260        }
[2385]261        ::Rappture::Drawing {
[2387]262            set mode "vtkviewer"
[2257]263            if {![info exists _mode2widget($mode)]} {
[2744]264                set servers [Rappture::VisViewer::GetServerList "vtkvis"]
[2387]265                set w $itk_interior.vtkviewer
[2744]266                Rappture::VtkViewer $w $servers
[1930]267                set _mode2widget($mode) $w
268            }
269        }
[1587]270        ::Rappture::Histogram {
271            set mode "histogram"
272            if {![info exists _mode2widget($mode)]} {
[2088]273                set w $itk_interior.histogram
[1587]274                Rappture::HistogramResult $w
275                set _mode2widget($mode) $w
276            }
277        }
278        ::Rappture::Curve {
279            set type [$dataobj hints type]
280            set mode "xy"
281            if { $type == "bars" } {
282                if {![info exists _mode2widget($mode)]} {
283                    set w $itk_interior.xy
[2565]284                    Rappture::BarchartResult $w
[1587]285                    set _mode2widget($mode) $w
286                }
287            } else {
288                if {![info exists _mode2widget($mode)]} {
289                    set w $itk_interior.xy
290                    Rappture::XyResult $w
291                    set _mode2widget($mode) $w
292                }
293            }
294        }
295        ::Rappture::Field {
[3573]296            if { ![$dataobj isvalid] } {
297                return;                 # Ignore invalid field objects.
298            }
[1587]299            set dims [lindex [lsort [$dataobj components -dimensions]] end]
300            switch -- $dims {
301                1D {
302                    set mode "xy"
303                    if {![info exists _mode2widget($mode)]} {
304                        set w $itk_interior.xy
305                        Rappture::XyResult $w
306                        set _mode2widget($mode) $w
307                    }
308                }
309                2D {
[3330]310                    set mode "field2d"
311                    set viewer [$dataobj viewer]
312                    set extents [$dataobj extents]
313                    if { $extents > 1 } {
314                        set mode "flowvis"
315                    }
[1587]316                    if {![info exists _mode2widget($mode)]} {
[3330]317                        set w $itk_interior.$mode
[1587]318                        if { ![winfo exists $w] } {
[3330]319                            Rappture::Field2DResult $w -mode $viewer
320                        }
[3523]321                        set _mode2widget($mode) $w
[1587]322                    }
323                }
324                3D {
[3330]325                    set mode [$dataobj viewer]
326                    set extents [$dataobj extents]
327                    if { $extents > 1 } {
328                        set mode "flowvis"
329                    }
[1587]330                    if {![info exists _mode2widget($mode)]} {
[3330]331                        set w $itk_interior.$mode
332                        Rappture::Field3DResult $w -mode $mode
[3524]333                        set _mode2widget($mode) $w
[1587]334                    }
335                }
336                default {
[3573]337                    puts stderr "WARNING: can't handle \"$dims\" dimension field"
[3571]338                    return
[1587]339                }
340            }
341        }
342        ::Rappture::Mesh {
[3573]343            if { ![$dataobj isvalid] } {
344                return;                 # Ignore invalid mesh objects.
345            }
[1587]346            switch -- [$dataobj dimensions] {
347                2 {
348                    set mode "mesh"
349                    if {![info exists _mode2widget($mode)]} {
350                        set w $itk_interior.mesh
351                        Rappture::MeshResult $w
352                        set _mode2widget($mode) $w
353                    }
354                }
355                default {
356                    error "can't handle [$dataobj dimensions]D field"
357                }
358            }
359        }
360        ::Rappture::Table {
361            set cols [Rappture::EnergyLevels::columns $dataobj]
362            if {"" != $cols} {
363                set mode "energies"
364                if {![info exists _mode2widget($mode)]} {
365                    set w $itk_interior.energies
366                    Rappture::EnergyLevels $w
367                    set _mode2widget($mode) $w
368                }
369            }
370        }
371        ::Rappture::LibraryObj {
372            switch -- [$dataobj element -as type] {
373                string - log {
374                    set mode "log"
375                    if {![info exists _mode2widget($mode)]} {
376                        set w $itk_interior.log
377                        Rappture::TextResult $w
378                        set _mode2widget($mode) $w
379                    }
380                }
381                structure {
382                    set mode "structure"
383                    if {![info exists _mode2widget($mode)]} {
384                        set w $itk_interior.struct
385                        Rappture::DeviceResult $w
386                        set _mode2widget($mode) $w
387                    }
388                }
389                number - integer {
390                    set mode "number"
391                    if {![info exists _mode2widget($mode)]} {
[2244]392                        set w $itk_interior.number
[1587]393                        Rappture::NumberResult $w
[3524]394                        set _mode2widget($mode) $w
[1587]395                    }
396                }
397                boolean - choice {
398                    set mode "value"
399                    if {![info exists _mode2widget($mode)]} {
400                        set w $itk_interior.value
401                        Rappture::ValueResult $w
[3524]402                        set _mode2widget($mode) $w
[1587]403                    }
404                }
405            }
406        }
407        ::Rappture::Image {
408            set mode "image"
409            if {![info exists _mode2widget($mode)]} {
410                set w $itk_interior.image
411                Rappture::ImageResult $w
412                set _mode2widget($mode) $w
413            }
414        }
415        ::Rappture::Sequence {
416            set mode "sequence"
417            if {![info exists _mode2widget($mode)]} {
418                set w $itk_interior.image
419                Rappture::SequenceResult $w
420                set _mode2widget($mode) $w
421            }
422        }
423        default {
[2387]424            error "don't know how to plot <$type> data [$dataobj info class]"
[1587]425        }
[11]426    }
427
428    if {$mode != $_mode && $_mode != ""} {
[1587]429        set nactive [llength [$_mode2widget($_mode) get]]
430        if {$nactive > 0} {
431            return  ;# mixing data that doesn't mix -- ignore it!
432        }
[11]433    }
[2088]434    # Are we plotting in a new mode? then change widgets
[11]435    if {$_mode2widget($mode) != [pack slaves $itk_interior]} {
[1587]436        # remove any current window
437        foreach w [pack slaves $itk_interior] {
438            pack forget $w
439        }
440        pack $_mode2widget($mode) -expand yes -fill both
[11]441
[1587]442        set _mode $mode
443        $_dispatcher event -idle !scale
[11]444    }
445    $_mode2widget($mode) add $dataobj $settings
446}
447
448# ----------------------------------------------------------------------
449# USAGE: _fixScale ?<eventArgs>...?
450#
451# Invoked automatically whenever a new dataset is added to fix the
452# overall scales of the viewer.  This makes the visualizer consistent
453# across all <dataobj> in this widget, so that it can plot all
454# available data.
455# ----------------------------------------------------------------------
456itcl::body Rappture::ResultViewer::_fixScale {args} {
457    if {"" != $_mode} {
[1587]458        set dlist ""
459        foreach slot $_dataslots {
460            foreach dobj $slot {
461                lappend dlist $dobj
462            }
463        }
464        eval $_mode2widget($_mode) scale $dlist
[11]465    }
466}
467
468# ----------------------------------------------------------------------
[193]469# USAGE: download coming
[464]470# USAGE: download controls <downloadCommand>
[193]471# USAGE: download now
[50]472#
473# Clients use this method to create a downloadable representation
474# of the plot.  Returns a list of the form {ext string}, where
475# "ext" is the file extension (indicating the type of data) and
476# "string" is the data itself.
477# ----------------------------------------------------------------------
[464]478itcl::body Rappture::ResultViewer::download {option args} {
[50]479    if {"" == $_mode} {
[1587]480        return ""
[50]481    }
[464]482    return [eval $_mode2widget($_mode) download $option $args]
[50]483}
484
485# ----------------------------------------------------------------------
[11]486# USAGE: _xml2data <xmlobj> <path>
487#
488# Used internally to create a data object for the data at the
489# specified <path> in the <xmlobj>.
490# ----------------------------------------------------------------------
491itcl::body Rappture::ResultViewer::_xml2data {xmlobj path} {
[2943]492    if {[info exists _xml2data($xmlobj-$path)]} {
493        return $_xml2data($xmlobj-$path)
494    }
495
[11]496    set type [$xmlobj element -as type $path]
497    switch -- $type {
[1587]498        curve {
[2943]499            set dobj [Rappture::Curve ::#auto $xmlobj $path]
[1587]500        }
[1930]501        datatable {
[2943]502            set dobj [Rappture::DataTable ::#auto $xmlobj $path]
[1930]503        }
[1587]504        histogram {
[2943]505            set dobj [Rappture::Histogram ::#auto $xmlobj $path]
[1587]506        }
507        field {
[2943]508            set dobj [Rappture::Field ::#auto $xmlobj $path]
[1587]509        }
510        mesh {
[2943]511            set dobj [Rappture::Mesh ::#auto $xmlobj $path]
[1587]512        }
513        table {
[2943]514            set dobj [Rappture::Table ::#auto $xmlobj $path]
[1587]515        }
516        image {
[2943]517            set dobj [Rappture::Image ::#auto $xmlobj $path]
[1587]518        }
519        sequence {
[2943]520            set dobj [Rappture::Sequence ::#auto $xmlobj $path]
[1587]521        }
522        string - log {
[2943]523            set dobj [$xmlobj element -as object $path]
[1587]524        }
525        structure {
[2943]526            set dobj [$xmlobj element -as object $path]
[1587]527        }
528        number - integer - boolean - choice {
[2943]529            set dobj [$xmlobj element -as object $path]
[1587]530        }
[2385]531        drawing3d - drawing {
[2943]532            set dobj [Rappture::Drawing ::#auto $xmlobj $path]
[1930]533        }
[1587]534        time - status {
[2943]535            set dobj ""
[1587]536        }
[2943]537        default {
538            error "don't know how to plot <$type> data path=$path"
539        }
[11]540    }
[2943]541
542    # store the mapping xmlobj=>dobj so we can find this result later
543    if {$dobj ne ""} {
544        set _xml2data($xmlobj-$path) $dobj
545    }
546    return $dobj
[11]547}
[22]548
549# ----------------------------------------------------------------------
[2943]550# USAGE: _cleanIndex <index>
551#
552# Used internally to create a data object for the data at the
553# specified <path> in the <xmlobj>.
554# ----------------------------------------------------------------------
555itcl::body Rappture::ResultViewer::_cleanIndex {index} {
[2977]556    set index [lindex $index 0]
[2943]557    if {[regexp {^#([0-9]+)} $index match num]} {
558        return [expr {$num-1}]  ;# start from 0 instead of 1
559    } elseif {[string is integer -strict $index]} {
560        return $index
561    }
562    error "bad plot index \"$index\": should be 0,1,2,... or #1,#2,#3,..."
563}
564
565# ----------------------------------------------------------------------
[22]566# CONFIGURATION OPTION: -width
567# ----------------------------------------------------------------------
568itcl::configbody Rappture::ResultViewer::width {
569    set w [winfo pixels $itk_component(hull) $itk_option(-width)]
570    set h [winfo pixels $itk_component(hull) $itk_option(-height)]
571    if {$w == 0 || $h == 0} {
[1587]572        pack propagate $itk_component(hull) yes
[22]573    } else {
[1587]574        component hull configure -width $w -height $h
575        pack propagate $itk_component(hull) no
[22]576    }
577}
578
579# ----------------------------------------------------------------------
580# CONFIGURATION OPTION: -height
581# ----------------------------------------------------------------------
582itcl::configbody Rappture::ResultViewer::height {
583    set h [winfo pixels $itk_component(hull) $itk_option(-height)]
584    set w [winfo pixels $itk_component(hull) $itk_option(-width)]
585    if {$w == 0 || $h == 0} {
[1587]586        pack propagate $itk_component(hull) yes
[22]587    } else {
[1587]588        component hull configure -width $w -height $h
589        pack propagate $itk_component(hull) no
[22]590    }
591}
Note: See TracBrowser for help on using the repository browser.