# copyright (C) 1997-2005 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

# $Id: stagraph.tcl,v 2.61 2005/04/28 22:41:20 jfontain Exp $


class dataStackedGraph {

    proc dataStackedGraph {this parentPath args} composite {[new frame $parentPath] $args} blt2DViewer {
        $widget::($this,path) [blt::barchart $widget::($this,path).graph -title {} -barmode stack -barwidth 0] 0 1
    } {
        set ($this,graphPath) $widget::($this,path).graph
        set graph [new bltGraph $($this,graphPath) $this]
        bltGraph::hideAxisAndCrossHair $graph 1
        $blt2DViewer::($this,menu) add checkbutton\
            -label $bltGraph::(menu,grid,label) -command "composite::configure $this -grid \$dataStackedGraph::($this,-grid)"\
            -variable dataStackedGraph::($this,-grid) -offvalue 0 -onvalue 1                         ;# add grid toggling menu entry
        menuContextHelp::set $blt2DViewer::($this,help) [$blt2DViewer::($this,menu) index end] $bltGraph::(menu,grid,help)
        after idle "dataStackedGraph::updateMessage $this"                        ;# delayed since it needs object to be constructed
        set ($this,graph) $graph
        composite::complete $this
    }

    proc ~dataStackedGraph {this} {
        delete $($this,graph)
        if {[string length $composite::($this,-deletecommand)] > 0} {
            uplevel #0 $composite::($this,-deletecommand)                                   ;# always invoke command at global level
        }
    }

    proc iconData {} {
        return {
            R0lGODdhJAAkAKUAAPj8+Hh4eDBgIDBgGHh8eKDocKjweJjgaJDYaIjQYAAAAIDIWHjAWHi4UHCwUHBIAGioSPDYSGCgQOjQSFiYQODISFCQOODESNi8SNC4
            SNCwSMisSMCkSMCcSLiYSBBgeLCQSLDY6ICEgKDQ4LCMSIiQiJjI2KiESJCYkIjA0KCkoIC4yKisqHiwwLi8uGiouMDEwGCgsMjQyNDY0ODk4Ojs6AAAAAAA
            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAJAAkAAAG/kCAcCgMEI/G4zBZVBKZSijy6RQ0lQLBwHrcbpdFglhQEBAC4jRBYGgbzGm22xyoQ8nlO77A
            L2f3fGYAUgACB4dchYeLjI2LgoQCCJMIf5SXmJmQRWhrCZ+goaKjoQIKYnVEAgusra6vsK4CRpEMtre4ubq4s4NYDcDBwsPEwpuDnQIOy8zNzs/NpqhQCg8Q
            EA/Z2dfc3d7cvYQPEdkR5uYPEurr7O0S4UoPE/P09A8U+Pna9/kUx3ViHlQYSLBgNgsWshl8gNCCtDNQHlyYSLHixGwWKWqDd+QBho8gQ4ocGfIBrXgZUqpc
            ybLlygcEfCELqKGmzZs4c958cAoi8JEHG4IKHUq06FCTMn9yWMq0qdOnTZGK60C1qtWrWK3ClKmgazUPYMOK9fCh7IexZM3y9NozYFkQcOPC/RCibogPcuna
            /aCGgIi/Ij6MGPGBhGHDZQcrJmx2MWG/gEtILvHBhOUPJzJXtsy5s+fLBCaXQEEaxYcUqFOYTc26tWu+pVGomK3iw4rbuHPr3p2bL20VLIKz+NCiuPHjyJMf
            5yuchYvnLj68mE69uvXr1flCdwGjO4wPMcKLH0++/Hi+3mHIWC/DrPv38OOXJcBexoz79/vq38+fAP4ZNAQo4IAEFmjggTUkqOCCDDbo4INBAAA7
        }
    }

    proc options {this} {
        set samples [expr {$global::graphNumberOfIntervals + 1}]
        return [list\
            [list -cellcolors {} {}]\
            [list -deletecommand {} {}]\
            [list -draggable 0 0]\
            [list -grid $global::graphDisplayGrid]\
            [list -height $global::viewerHeight]\
            [list -interval 0 0]\
            [list -labelsposition right]\
            [list -plotbackground $global::graphPlotBackground]\
            [list -samples $samples $samples]\
            [list -width $global::viewerWidth]\
            [list -xlabelsrotation $global::graphXAxisLabelsRotation]\
            [list -ymaximum {} {}]\
            [list -ymaximumcell {} {}]\
        ]
    }

    proc set-cellcolors {this value} {                             ;# colors of soon to be created cells when initializing from file
        if {$composite::($this,complete)} {
            error {option -cellcolors cannot be set dynamically}
        }
        blt2DViewer::setCellColors $this $value
    }

    proc set-deletecommand {this value} {}

    proc set-grid {this value} {
        if {$value} {
            $($this,graphPath) grid configure -hide no
        } else {
            $($this,graphPath) grid configure -hide yes
        }
        set ($this,-grid) $value                                                ;# so that corresponding popup menu entry is updated
    }

    foreach option {-height -width} {
        proc set$option {this value} "\$widget::(\$this,path) configure $option \$value"
    }

    proc set-draggable {this value} {
        if {$composite::($this,complete)} {
            error {option -draggable cannot be set dynamically}
        }
        if {$value} {
            blt2DViewer::allowDrag $this
            bltGraph::allowDrag $($this,graph) $blt2DViewer::($this,drag)                                     ;# extend drag formats
        }
    }

    proc set-interval {this value} {
        set graph $($this,graph)
        bltGraph::setRange $graph [expr {($composite::($this,-samples) - 1) * $value}]
        bltGraph::xAxisUpdateRange $graph
        bltGraph::xUpdateGraduations $graph
    }

    proc set-samples {this value} {                                                                     ;# stored at composite level
        if {$composite::($this,-interval) == 0} return                                           ;# useless in database history mode
        set graph $($this,graph)
        bltGraph::setRange $graph [expr {($value - 1) * $composite::($this,-interval)}]
        bltGraph::xAxisUpdateRange $graph
        bltGraph::xUpdateGraduations $graph
    }

    proc set-labelsposition {this value} {
        blt2DViewer::updateLayout $this
    }

    proc set-plotbackground {this value} {
        $($this,graphPath) configure -plotbackground $value
        $($this,graphPath) grid configure -color [visibleForeground $value]
    }

    proc set-xlabelsrotation {this value} {
        bltGraph::xRotateLabels $($this,graph) $value
    }

    proc set-ymaximum {this value} {
        blt2DViewer::setLimit $this maximum $value
    }
    proc set-ymaximumcell {this value} {
        blt2DViewer::setLimitCell $this maximum $value
    }

    proc updateTimeDisplay {this seconds} {
        bltGraph::xAxisUpdateRange $($this,graph) $seconds
    }

    proc newElement {this path args} {                                                          ;# invoked from 2D viewer base class
        return [eval new element $path $args]
    }

    proc updateElement {this element seconds value} {                           ;# value is either a valid number or the ? character
        element::update $element $seconds $value
    }

    proc canMonitor {this array} {
        if {$composite::($this,-interval) > 0} {
            return 1                                                                                             ;# not history mode
        } else {                              ;# check that cells belong to instance module, and not summary table data, for example
            return [string equal [lindex [modules::decoded [modules::namespaceFromArray $array]] 0] instance]
        }
    }

    proc update {this array} {                      ;# update display using cells data (implementation identical to dataGraph class)
        if {$composite::($this,-interval) > 0} {                                                                 ;# not history mode
            return [blt2DViewer::_update $this $array]                                                    ;# use base implementation
        }
        foreach element $blt2DViewer::($this,elements) {                                                             ;# history mode
            set cell $blt2DViewer::($this,cell,$element)
            foreach {start end} [databaseInstances::range $cell] {}           ;# database cell range in seconds (limited by cursors)
            if {[string length $start] == 0} {                                                                    ;# no history data
                element::range $element {}                                                                       ;# let element know
                continue
            }
            set start [clock scan $start]
            set end [clock scan $end]
            if {($element::($element,start) == $start) && ($element::($element,end) == $end)} {
                continue                                                ;# no range change: avoid potentially lengthy database query
            }
            element::range $element [databaseInstances::history $cell]                                    ;# (inside cursors limits)
        }
        foreach {minimum maximum} [databaseInstances::cursorsRange] {}                                                 ;# in seconds
        set graph $($this,graph)
        bltGraph::setRange $graph [expr {$maximum - $minimum}]
        bltGraph::xUpdateGraduations $graph
        bltGraph::xAxisUpdateRange $graph $maximum
        if {[info exists cell]} {                                                 ;# no maximum can exist when there are no elements
            blt2DViewer::updateLimit $this maximum $array                      ;# note: maximum cell will take value at end of range
        }
    }

    proc modified {this monitored} {                                                                    ;# number of monitored cells
        bltGraph::hideAxisAndCrossHair $($this,graph) [expr {$monitored == 0}]                     ;# hide if no elements to display
        updateMessage $this
    }

    proc updateMessage {this} {
        if {[llength [blt2DViewer::cells $this]] == 0} {
            centerMessage $widget::($this,path)\
                [mc "stacked graph chart:\ndrop data cell(s)"] $composite::($this,-plotbackground) $global::viewerMessageColor
        } else {
            centerMessage $widget::($this,path) {}
        }
    }

    proc initializationConfiguration {this} {
        return [concat\
            [list\
                -ymaximum $composite::($this,-ymaximum) -ymaximumcell $composite::($this,-ymaximumcell)\
                -labelsposition $composite::($this,-labelsposition) -grid $composite::($this,-grid)\
            ] [blt2DViewer::_initializationConfiguration $this]\
        ]
    }

    virtual proc updateLabels {this} {
        blt2DViewer::_updateLabels $this [expr {$composite::($this,-interval) > 0}]             ;# no values display in history mode
    }

}


class dataStackedGraph {

    class element {

        proc element {this path args} switched {$args} {
            variable x$this
            variable y$this

            blt::vector create x${this}                                                                        ;# x axis data vector
            blt::vector create y${this}                                                                        ;# y axis data vector
            # use object identifier as element identifier and make sure bar width is 1 pixel:
            $path element create $this -label {} -xdata x$this -ydata y$this -borderwidth 0 -barwidth 0.001
            set ($this,start) 0; set ($this,end) 0                                                      ;# used in history mode only
            set ($this,path) $path
            switched::complete $this
        }

        proc ~element {this} {
            variable x$this
            variable y$this

            blt::vector destroy x$this y$this
            $($this,path) element delete $this
            if {[string length $switched::($this,-deletecommand)] > 0} {
                uplevel #0 $switched::($this,-deletecommand)                                ;# always invoke command at global level
            }
        }

        proc options {this} {
            return [list\
                [list -color black black]\
                [list -deletecommand {} {}]\
            ]
        }

        proc set-color {this value} {
            $($this,path) element configure $this -foreground $value
        }

        proc set-deletecommand {this value} {}                                                   ;# data is stored at switched level

        proc update {this x y} {                                                    ;# y is either a valid number or the ? character
            variable x$this
            variable y$this

            set length [llength [x$this search 0 [$($this,path) xaxis cget -min]]]
            incr length -2          ;# scroll by removing points to the left of the x axis minimum (fails when there is no data yet)
            catch {x$this delete :$length; y$this delete :$length}
            set length [x$this length]
            x$this append $x                                                                                     ;# append new value
            if {[string equal $y ?]} {                                                                                 ;# void value
                y$this append 0
            } else {
                y$this append [expr {abs($y)}]                                   ;# negative values make no sense in a stacked setup
            }
            if {$length > 0} {                                                                                  ;# after first point
                fill $this [expr {$length - 1}] $length                                             ;# eventually fill last interval
            }
        }

        proc range {this list} {                           ;# set a whole set of points at once. list is flat: timestamp, value, ...
            variable x$this
            variable y$this

            ### note: do not use the following code since that causes parasite lines to be displayed (using BLT 2.4z):
            # catch {x$this delete 0 end; y$this delete 0 end}
            blt::vector destroy x$this y$this                                           ;# clear first so only new data is displayed
            blt::vector create x${this}
            blt::vector create y${this}
            foreach {stamp value} $list {                                        ;# assumes that timestamps are ordered increasingly
                if {[string length $value] == 0} continue                                                            ;# invalid data
                x$this append [clock scan $stamp]
                y$this append [expr {abs($value)}]                               ;# negative values make no sense in a stacked setup
            }
            $($this,path) element configure $this -xdata x$this -ydata y$this
            if {[llength $list] == 0} {                                                                         ;# no samples at all
                set ($this,start) 0
                set ($this,end) 0
            } else {                                                       ;# remember whole data (even including voids) time limits
                set ($this,start) [clock scan [lindex $list 0]]                                 ;# remember range extents separately
                set ($this,end) [clock scan [lindex $list end-1]]              ;# (since null values are not saved in vectors above)
            }
        }

        proc fill {this from to} {                      ;# fill vectors range so that there is at least 1 point per horizontal pixel
            variable x$this
            variable y$this

            set path $($this,path)
            blt::vector create add
            # create a vector with one value per pixel
            set step [expr {double([$path xaxis cget -max] - [$path xaxis cget -min]) / [$path extents plotwidth]}]
            add seq [x${this} index $from] [x${this} index $to] $step
            add delete 0                                                        ;# remove values that already exist in source vector
            if {[add length] == 0} {                                               ;# there was no room for some intermediate points
                blt::vector destroy add
                return 0                                                                                               ;# no filling
            }
            if {[add index end] == [x${this} index $to]} {add delete end}                      ;# remove end value if it is included
            if {[add length] == 0} {                                               ;# there was no room for some intermediate points
                blt::vector destroy add
                return 0                                                                                               ;# no filling
            }
            set after [x${this} index $to:end]
            x${this} set [x${this} index 0:$from]; x${this} append add; x${this} append $after                  ;# insert new points
            blt::vector create limits
            limits append [y${this} index $from] [y${this} index $to]
            limits populate add [add length]                                                  ;# interpolate between vertical limits
            add delete 0 end                                                    ;# remove values that already exist in source vector
            set after [y${this} index $to:end]
            y${this} set [y${this} index 0:$from]; y${this} append add; y${this} append $after                  ;# insert new points
            blt::vector destroy add limits
            return 1                                                                                              ;# successful fill
        }

    }

}
