# copyright (C) 1997-2006 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: predictor.tcl,v 1.40 2006/02/12 01:26:05 jfontain Exp $


# note: a lot of code is copied from dataGraph class


class predictor {

    set (list) {}
    set (defaultElementColor) [lindex $global::viewerColors 0]
    set (prepare,icon) [image create photo -data {
        R0lGODlhFgAWAOeSAAAAAAICAgMDAwYGBgcHBwwMDA4ODhAQEBISEhQUFBYWFhkZGRoaGhwcHCEhISQkJCgoKC4uLjAwMDIyMjc3Nzs7Oz09PUlJSU1NTU5O
        TlZWVldXV11dXWNjY2VlZWdpZ2pqam1tbW5ubnJycnZ3dnZ5dnd5d3t9e35+fn5/foCCgICDgIKCgoGDgYODg4KEgoSHhIeJh4iLiIqKiomMiYyMjJKSkpOV
        k5WVlZSXlJaWlpWXlZmZmZqampmcmZ2dnZ6enqCgoKOjo6Ono6ioqKmpqamrqaurq6ysrKquqqywrK2xra6yrrGxsbKysrC1sLOzs7G2sbS0tLK2srW1tbO3
        s7a2trS4tLS5tLe3t7i4uLa7trq6uri9uLu7u729vbu/u76+vsDAwL/Ev8LCwsPDw8HGwcLHwsXFxcPHw8PIw8fHx8XJxcfIx8XKxcjIyMrKysjNyMnOyczM
        zM7Ozs/Pz9DQ0NTU1NXV1dbW1tfX19jY2Nrb2tvb29zc3N3d3d7e3t/f3+Hh4ePj4+Xl5efn5+jo6Onp6ezs7O3t7e7u7vHx8fPz8/T09PX19fb29vn5+f39
        /f//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////////yH5BAEKAP8ALAAAAAAWABYAAAj+AP8JHChwzJUl
        Sp6AIchwIBMjfCApIuSnj6BEiejsUNJQTRtJh77gQOEBgwUJECqIWDPoBcNCkvLs6cEhQoIBAHLq7PCHxEAfkoSUmdFAwAADChYwWICAQE4tR7YIvCGphs4A
        Dipk2KDhwoQCOf2ESSJwCCJJIHSqVXtAjKQQcgbKWCRJUpAND5wCQEBhBJm6KaowpAGpruHDkvAM4kG2oQpJjAIpetToEB4oLJwgodFwYAtJhrxQKQLEhos3
        eGB0JvhC0iAuUojoqJOnxGqGMCTtKfLjziITtxvGiPQli6MPwTtyiSSJDpbkDONkoWtHKvSBbLIIWgRnyvWBbqQn6CG0xvv3f2es0AGEJsr5f2mszPlThsn7
        fyeahAFh5v6/HCt0cVtAADs=
    }]
    set (fit,icon) [image create photo -data {
        R0lGODlhFgAWAOe9AJVaI5JeL6RiH21ti59qMKpoIHJyj3R0kJpvRnZ3kphzUHd5kKB0R69zJnt7lrV2JKZ4R4GBmqt8R4SEnc17GLGBSImKn7aFSLiGSI2N
        o7yKSJCQpteHGZGTorWOXMGOSJSVqcuRKqqTepSWppWVq+KLEK2VesiTSJmZrZmZrumQENSXJ/+JBJycsJ2dsc6YSO6TENiaJ9qaJJ+fs9mdJfiTDcSca9ud
        KKGhsv+RCNOcSf+SCf2TC/+TCv+UCtyhJv+VC+2dFP+WC7yjgKWlt9+lJqenufGgFfidEs2ja6eouOOmJv+cEM2kbqirs+KoJ9Claf+eEKurvOKrJ6usuP+h
        Equut+GsMP2jFKyuuv+kFP+lFOSvKf+mFfOsGvWsGv+oFq+zuP+pFbCzuuWzK/+qGLG0u7O1v7O2vee3LLW1xLa2xf6yHf+yG/+zHbe5wtO3hv+1Hrm5yLi8
        vri8v/+2H7u7yfy6Ibq+wLy8yby9yLy+xr6+y/+9I/+9JL7Dw8DCycDExf/CJsLCz//DJ8PDz8HGxcLFyMLGxsTE0MPHx8TIx8TIyMfH0sjI08nLw//MLMrL
        w8nJ1MvMw8zMw8zNw83Nw//QLs3Ow87Ow8zM1srPzM7Pw//SMM3N187O187O2NDQ2tHR2tLS2//eN9XV3dXV3tbW3tnZ4dra4dvb497e5N7e5eHh5+Li5+Li
        6OXl6ufn7Ojo7ezs8O7u8u/v8vHx9PLy9fT09vX19/r6+/v7/P7+/v//////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////////yH5BAEKAP8ALAAAAAAWABYAAAj+AP8J/KenEapQ
        gwBtGsiw4cA9oHpJnGXnkMOLAt9IkthLlxxGGC+eScTRlpKQWRQ1pBJKIq88izDOaIFoICIjt3bp9IQGI5FWIAL9C4RjVq5cuHCJsoLRRa1UGcaggEWratU1
        ZjBmiCVrVIpVssKKJUEH4wg1r9K+csXWFStHiSzA4eTQUIJPqvLqRcXHTiEoV3RkcojngBRRpkyVKqWpkaYkN0qkeYHJ4ZwFAyZsiOCg0akmMVTwgEHmhKWL
        f8J0COPEgI0VQbBo0XKEy4dKIQUOCeGFjZvfbr5M0YA7ZCUMTwQJIkTIT587Sy5Qyj2pQpFLnQjV6VLFjQwPuf8+TZLwg1SdMjmY+GjTILx4CDTcxGGxo4eY
        B+7/RWLAYQskMFEgQUB++iFAARBC1FCACQT+84gCAAgQgAj/BAQAOw==
    }]
    set (fit,icon,abort) [image create photo -data {
        R0lGODlhFgAWAKU7AMQAAMUDAMYHAMcJAMkOAMoPAOkAAMwRAM0XAOkJAP4AANEbANEeAP4KANMhANYmAOodA9osAP4aAt4yAOovCOI4AP4pBuY/AP41C+hD
        AO5DEOtHEv5AEOxMFe5LFf5LFu1aHf5WHe5fH/FlJf5gJfFsKv5pLO9yLf5yNPB9Nf57PPKHPf6ERfOQRf6LTf6TVvWaTfadUP6bX/6hZv6ocP6ueP60gP66
        if7Akf7Gmv7Lov///////////////////yH5BAEKAD8ALAAAAAAWABYAAAauwJ9wSCwaj8jkMHNRMhbHTAxWQTJEIERRqsu1JsarCtU5EC+wHO62ihDFKNOm
        UKy0brZa6iGEy+lGEys1NDMnDn5zSREpMzIvJSNjf0o/DycvLiyTipU/DiWbcR6AngyScSQaBJ4/fiQhHxQDlX4eGh8cGBACSYkFBBQYFhIJAUe/QgMQEg0K
        BgBFyUMCCQrX0EQLIHGdRAEG2NFECB3eRQAG2UYHpUcA463y80RBADs=
    }]
    set (fit,icon,findPeriod) [image create photo -data {
        R0lGODlhEAAQAOeBACtw/S5z/zV4/jZ4/z558Tx8/z58+kN87j1+/0N+9EB//0GA/0iA8EiA8UuC80iD/kmE/EqF/VKF6k6F9VCG9FCH9E6H/E+I/lSK91OL
        /lmL7lqL7liM9FaM+1aM/FmM9FSN/12N7lWO/1aO/lqQ/lyQ+l2Q+l+Q9lqR/1uR/VuR/1yR/l2S/16T/16U/l+U/WCU/2mV7mOW/mma/G2c/Gyd/22d/26e
        /3Cf/3eg9Hmj+X6n+Xyo/4+p2ZKr24as94es+Iet+oSu/4eu/Zau24iw/4yy/5uy25S3/qa306a31Jq5+Zq8/6271K681KC//qbD/6nF/arF/arG/qzH/8TI
        y7DK/8bKy8fKy7bN/LfN+rXO/83NyM3OyM7OyLvQ+b/Q9LzS/r7S+8XU8sLW/sfX+MjY+sra+c/e/NDf/tTg+Nji+Nfj/dbk/trk+Nzk9tnl/dzm+N3m993n
        +97p/97q/+Lr/uLs/+vv+Orx/+3y++7y/PD1//j6//z9//3+/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEKAP8ALAAAAAAQABAAAAjkAP8JHIjFiZMr
        AxMOPMJhQQkTBSoQUfjPS4wMUey4WTPnS4cQXRLGmJHmTBxAgPaoaZNjw8ILYcToAZTnjh9AaMhY8CGQwxArWgDxsWKlDCA6U3Yk+IdFARImT+psYbLkDSAp
        I0AEqOIkgpCvX3/IAQQnBQgQA5Q0eVCjbQ0bYwCxeSGirgAlVxC0cNGChQo8fWSgGIwCQJV/FFbAWAwjCxXGMEgQEEgEwo3LNMCYwXH5hoEeA0Oc4MFDxx9A
        RUh/kJCwiwYMQYxAoWIEyAQJXCj6aODAQwcGB0BTHFhFSZLDCgMCADs=
    }]
    set (predict,icon) [image create photo -data {
        R0lGODlhFgAWAOeQADOfyjGhxjWgzDOixzWjyDalyUGgxjimyjmny0SiyDuozDypzUily0anxz6qzkeoyD+r0EqnzUiqyUyoz0Kt0kusy0+s0k6uzU+vzlCw
        z1Gx0FqtzlOy0Vuvz12w0Fa11WCz1GG01WK11mS212m10GW32HG1y2a42Wy30nK2zW2403O4zm+61nC713G82HK92XO+2ni903S/23m+1HbA3HvA1nfC3Yu8
        yX3B137C2X/D2oDE24HG3ILH3YnE3JLCz4PI3orF3YzH35vD0ZHH2Y7I4I/K4pDL45/H1pHM5JLN5ZfN35PO5pjO4JnP4ZrQ4rDJzZvR47LKzpzS5LbLyp3T
        5p7U56XS5p/V6LnOzabT56DW6a7S27rPzqfU6KHX6qjV6abX5K7X5a/Y5rzV2cXS0rDZ6L3W2sbT07Ha6bLb6rTc67Xd7Lbe7bfg7rjh77/f78Dg8b7j7MXh
        7Mfj7sjk78nl8Mrn8cvo8s3q9dTo9c/r9tPs8drq8dTt8tvr8tzs893u9N7v9d/w9uDx+OHy+ePz+ubz9OT0++f09ej19un29+/19/L3+fP4+/7//P//////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////////yH5BAEKAP8ALAAAAAAWABYAAAj+AP8JHEiwoMGD
        /8pwebKkCBEkXRASRCNmzJgvYZgAKWIjRhaJZ9a4gUNyjJgpRXbAODHkIBk2c+rcqRPnzZovTIrkUBHiR8EycObg8TMokB89bcZMWWJDBokOVAiKYWMHkKFF
        jAT1ibNmipMeMkpY6DCwTBg5dwIxesQokR85a7DklKGCgwMpAsmAcZNWkaNGiQLZgXPlSA8YKjw4SCGwCRg2dPoYQmRI6xylSXispMBgQ2MxYd7o+RPoDx45
        SaMU0QEDBYYDBAQCYQJmjRw5durMiQNmKRAbLUZ0YCBAIBElUa6sWcNmTZgxYHIWOSzCAYEGAn/0OOIkipctW3qSKylyZMkVIiQ2GGCc0IaNIkCaXEkyHogP
        h4KYeEBgAMpAHO4FwcMOPfigg3swBOGEDxwIgABBZYzQwgk2+PDCCy2ogIIKJ6gwwgX9FTTEByiI8EEJI5jIgQUeTEDcDQf9kIEHHHDQQQcWbDDBAQEEACNC
        VHjwwAQLIIAAAAEI0IB/EglERQoTJHCdCVE1aeVBAQEAOw==
    }]

    proc predictor {this parentPath args} composite {[new frame $parentPath] $args} blt2DViewer {
        $widget::($this,path) [blt::stripchart $widget::($this,path).graph -title {}] 5 0 1
    } {
        if {![info exists (interpolate,methods)]} {
            set (interpolate,methods,codes) [list spline linear]
            set (interpolate,methods) [list [mc spline] [mc linear]]
            set (fit,methods,codes) [list automatic ARIMA neural]
            set (fit,methods) [list [mc automatic] ARIMA [mc neural]]
            set (errorTitle) [mc {moodss: Predictor error}]
        }
        set graphPath $widget::($this,path).graph
        $graphPath pen create void -linewidth 0 -symbol none                                                  ;# pen for void values
        $graphPath grid configure -minor 0
        $graphPath x2axis configure -max 0 -showticks 0 -hide true
        set graph [new bltGraph $graphPath $this]
        bltGraph::hideAxisAndCrossHair $graph 1
        $blt2DViewer::($this,menu) add checkbutton\
            -label $bltGraph::(menu,grid,label) -command "composite::configure $this -grid \$predictor::($this,-grid)"\
            -variable predictor::($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 "predictor::updateMessage $this"                               ;# delayed since it needs object to be constructed
        set ($this,graphPath) $graphPath
        set ($this,graph) $graph
        composite::complete $this
        createControls $this [blt2DViewer::bottomFrame $this 0]
        bind $graphPath <Configure> "+ predictor::refreshElements $this"                                       ;# track size updates
        lappend (list) $this
    }

    proc ~predictor {this} {
        arimaAbort $this                                                                       ;# cancel any calculation in progress
        neuralAbort $this
        if {[info exists ($this,widgets)]} {
            eval delete $($this,widgets); unset ($this,widgets)
        }
        delete $($this,graph)
        if {$composite::($this,-deletecommand) ne ""} {
            uplevel #0 $composite::($this,-deletecommand)                                   ;# always invoke command at global level
        }
        ldelete (list) $this
    }

    proc options {this} {
        return [list\
            [list -aggregateby 0 0]\
            [list -cellcolors {} {}]\
            [list -deletecommand {} {}]\
            [list -draggable 0 0]\
            [list -fitmethod automatic automatic]\
            [list -fitlongperiod {} {}]\
            [list -fitparameters {} {}]\
            [list -fitperiod {} {}]\
            [list -fittedcolor {} {}]\
            [list -fitteddisplay 0 0]\
            [list -grid $global::graphDisplayGrid]\
            [list -height 300]\
            [list -interpolationmethod spline spline]\
            [list -labelsposition top]\
            [list -predictedcolor {} {}]\
            [list -predictduration 0 0]\
            [list -preparedcolor {} {}]\
            [list -prepareddisplay 0 0]\
            [list -plotbackground $global::graphPlotBackground]\
            [list -smoothdegree 0 0]\
            [list -sourcecolor {} {}]\
            [list -sourcedisplay 1 1]\
            [list -width $global::viewerWidth]\
            [list -xlabelsrotation $global::graphXAxisLabelsRotation]\
            [list -ymaximum {} {}]\
            [list -ymaximumcell {} {}]\
            [list -yminimum $global::graphMinimumY]\
            [list -yminimumcell {} {}]\
        ]
    }

    foreach option [list\
        -aggregateby -fittedcolor -fitteddisplay -interpolationmethod -smoothdegree -fitmethod -fitparameters -fitperiod\
        -fitlongperiod -predictduration -predictedcolor -preparedcolor -prepareddisplay -sourcecolor -sourcedisplay\
    ] {
        proc set$option {this value} {}
    }

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

    proc set-deletecommand {this value} {}

    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-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
    }

    proc set-labelsposition {this value} {
        if {$composite::($this,complete)} {
            error {option -labelsposition cannot be set dynamically}
        }
        if {$value ne "top"} {error {only top labels position is supported}}
        blt2DViewer::updateLayout $this
    }

    proc set-plotbackground {this value} {
        $($this,graphPath) configure -plotbackground $value
        $($this,graphPath) pen configure void -color $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 set-yminimum {this value} {
        blt2DViewer::setLimit $this minimum $value
    }
    proc set-yminimumcell {this value} {
        blt2DViewer::setLimitCell $this minimum $value
    }

    proc newElement {this path args} {                                                          ;# invoked from 2D viewer base class
        if {[llength $blt2DViewer::($this,elements)] > 0} {
            eval delete $blt2DViewer::($this,elements)                             ;# this viewer can only handle a single data cell
        }
        set element [eval new element $path $args]
        blt2DViewer::bottomFrame $this 1
        reset $this                                                        ;# this is when saved options are used for initialization
        set ($this,display,source) 1                                                     ;# obviously since element was just created
        set color [switched::cget $element -color]
        $($this,prepare,widget,label,source) configure -background $color
        if {$composite::($this,-fitmethod) ne "automatic"} {                              ;# previously saved configuration: restore
            set ($this,restore) {}
        }
        return $element
    }

    proc deletedElement {this array element} {
        blt2DViewer::_deletedElement $this $array $element
        blt2DViewer::bottomFrame $this 0
        foreach option [list\
            -aggregateby -fittedcolor -fitteddisplay -interpolationmethod -smoothdegree -fitmethod -fitparameters -fitperiod\
            -fitlongperiod -predictduration -predictedcolor -preparedcolor -prepareddisplay -sourcecolor -sourcedisplay\
        ] {
            composite::configure $this $option [lindex [composite::configure $this $option] 1]      ;# reset to option initial value
        }
        $($this,prepare,widget,label,source) configure -background $widget::option(label,background)
        $($this,prepare,widget,label,prepared) configure -background $widget::option(label,background)
        $($this,fit,widget,label,fitted) configure -background $widget::option(label,background)
        $($this,predict,widget,label,predicted) configure -background $widget::option(label,background)
        reset $this                                                                        ;# since there is nothing left to display
    }

    proc canMonitor {this array} {             ;# check that cell 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 cell data
        if {[llength $blt2DViewer::($this,elements)] == 0} return
        set element [lindex $blt2DViewer::($this,elements) 0]                              ;# this viewer handles a single data cell
        set cell $blt2DViewer::($this,cell,$element)
        foreach {start end} [databaseInstances::range $cell] {}               ;# database cell range in seconds (limited by cursors)
        if {$start eq ""} {                                                                                       ;# no history data
            clearFittedPredicted $this
            element::range $element {}                                                                           ;# let element know
        } else {
            set start [clock scan $start]
            set end [clock scan $end]
            if {($element::($element,start) == $start) && ($element::($element,end) == $end)} {
                refreshElements $this                                   ;# no range change: avoid potentially lengthy database query
            } else {
                clearFittedPredicted $this
                set list [databaseInstances::history $cell]                                               ;# (inside cursors limits)
                set points 0; set missing 0
                set instants {}; set values {}
                foreach {stamp value} $list {                                    ;# assumes that timestamps are ordered increasingly
                    incr points
                    if {$value eq ""} {                   ;# void value (instead of ? since data is assumed to come from a database)
                        incr missing
                        continue
                    }
                    set seconds [clock scan $stamp]
                    if {[info exists last] && ($seconds == $last(seconds))} {                                           ;# duplicate
                        if {![info exists duplicate]} {
                            set duplicate(value) [expr {double($last(value))}]; set duplicate(count) 1
                        }
                        set duplicate(value) [expr {$duplicate(value) + $value}]; incr duplicate(count)
                        continue                                                             ;# consider duplicates as single points
                    }
                    if {[info exists duplicate]} {                                           ;# replace last value by averaged value
                        set values [concat [lreplace $values end end] [expr {$duplicate(value) / $duplicate(count)}]]
                        unset duplicate
                    }
                    lappend instants $seconds
                    lappend values $value
                    set last(seconds) $seconds
                    set last(value) $value
                }
                if {[info exists duplicate]} {                                                         ;# last points were duplicate
                    set values [concat [lreplace $values end end] [expr {$duplicate(value) / $duplicate(count)}]]
                }
                set ($this,sourced,points) $points
                set ($this,sourced,missing) $missing
                set ($this,sourced,instants) $instants                                                 ;# not including void entries
                set ($this,sourced,values) $values
                foreach [list ($this,sourced,minimum) ($this,sourced,maximum) ($this,sourced,normalized)] [normalize $values] {}
                element::range $element $list
                if {[info exists ($this,last,interpolate,method)]} {    ;# force recalculations if preparation has been done already
                    unset ($this,last,interpolate,method)
                    prepare $this
                }
            }
        }
        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
            blt2DViewer::yAxisUpdate $this                    ;# since vertical axis position can change due to tick labels changing
        }
        refreshControls $this
        if {[info exists ($this,restore)]} {                                                       ;# previously saved configuration
            unset ($this,restore)
            # wait till predictor interface is fully visible for states to be readable by user
            bind $($this,graphPath) <Expose> "bind $($this,graphPath) <Expose> {}; predictor::restore $this"
        }
    }

    proc xUpdateAxis {this minimum maximum} {
        set graph $($this,graph)
        bltGraph::setRange $graph [expr {$maximum - $minimum}]
        bltGraph::xUpdateGraduations $graph
        bltGraph::xAxisUpdateRange $graph $maximum
    }

    proc restore {this} {
        prepare $this
        if {[info exists ($this,prepared,normalized)]} {
            fitModel $this $composite::($this,-fitmethod) [split $composite::($this,-fitparameters) ,]
        }
        foreach name {source prepared fitted} {
            set ($this,display,$name) $composite::($this,-${name}display)
            displayElement $this $name
        }
    }

    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 "predictor:\ndrop data cell"] $composite::($this,-plotbackground) $global::viewerMessageColor
        } else {
            centerMessage $widget::($this,path) {}
        }
    }

    proc initializationConfiguration {this} {
        # save data resulting from successful operations (also see refreshControls{})
        if {[info exists ($this,interpolated,method)]} {
            set index [lsearch -exact $(interpolate,methods) $($this,interpolated,method)]
            set interpolationMethod [lindex $(interpolate,methods,codes) $index]
        } else {
            set interpolationMethod [lindex [composite::configure $this -interpolationmethod] 1]                          ;# default
        }
        if {[info exists ($this,aggregated,by)]} {
            set aggregateBy $($this,aggregated,by)
        } else {
            set aggregateBy [lindex [composite::configure $this -aggregateby] 1]                                          ;# default
        }
        if {[info exists ($this,smoothed,degree)]} {
            set smoothDegree $($this,smoothed,degree)
        } else {
            set smoothDegree [lindex [composite::configure $this -smoothdegree] 1]                                        ;# default
        }
        if {[info exists ($this,fitted,method)]} {
            set index [lsearch -exact $(fit,methods) $($this,fitted,method)]
            set fitMethod [lindex $(fit,methods,codes) $index]
        } else {
            set fitMethod [lindex [composite::configure $this -fitmethod] 1]                                              ;# default
        }
        if {[info exists ($this,fitted,parameters)]} {
            set fitParameters [join $($this,fitted,parameters) ,]                                   ;# store as comma separated list
        } else {
            set fitParameters [lindex [composite::configure $this -fitparameters] 1]                                      ;# default
        }
        if {[info exists ($this,fitted,period)]} {
            set fitPeriod $($this,fitted,period)
        } else {
            set fitPeriod [lindex [composite::configure $this -fitperiod] 1]                                              ;# default
        }
        if {[info exists ($this,fitted,longPeriod)]} {
            set fitLongPeriod $($this,fitted,longPeriod)
        } else {
            set fitLongPeriod [lindex [composite::configure $this -fitlongperiod] 1]                                      ;# default
        }
        if {[llength $blt2DViewer::($this,elements)] > 0} {
            set sourceColor [$($this,prepare,widget,label,source) cget -background]
        } else {
            set sourceColor {}
        }
        if {[info exists ($this,element,prepared)]} {
            set preparedColor [$($this,prepare,widget,label,prepared) cget -background]
        } else {
            set preparedColor {}
        }
        if {[info exists ($this,element,fitted)]} {
            set fittedColor [$($this,fit,widget,label,fitted) cget -background]
        } else {
            set fittedColor {}
        }
        if {[info exists ($this,element,predicted)]} {
            set predictedColor [$($this,predict,widget,label,predicted) cget -background]
        } else {
            set predictedColor {}
        }
        return [concat\
            [list\
                -aggregateby $aggregateBy -interpolationmethod $interpolationMethod -smoothdegree $smoothDegree\
                -fitmethod $fitMethod -fitparameters $fitParameters -fitperiod $fitPeriod -fitlongperiod $fitLongPeriod\
                -predictduration [durationInput::get $($this,predict,widget,duration)]\
                -sourcecolor $sourceColor -preparedcolor $preparedColor -fittedcolor $fittedColor -predictedcolor $predictedColor\
                -fitteddisplay $($this,display,fitted) -prepareddisplay $($this,display,prepared)\
                -sourcedisplay $($this,display,source) -grid $composite::($this,-grid)\
                -ymaximum $composite::($this,-ymaximum) -ymaximumcell $composite::($this,-ymaximumcell)\
                -yminimum $composite::($this,-yminimum) -yminimumcell $composite::($this,-yminimumcell)\
            ] [blt2DViewer::_initializationConfiguration $this]\
        ]
    }

    proc refreshElements {this} {
        set element [lindex $blt2DViewer::($this,elements) 0]
        if {$element eq ""} return
        element::refresh $element
        if {[info exists ($this,element,prepared)]} {
            element::refresh $($this,element,prepared)
        }
        if {[info exists ($this,element,fitted)]} {
            element::refresh $($this,element,fitted)
        }
        if {[info exists ($this,element,predicted)]} {
            element::refresh $($this,element,predicted)
        }
    }

    proc normalize {values} {                                                                              ;# no void values allowed
        if {[llength $values] == 0} {
            return [list {} {} {}]
        }
        foreach value $values {
            if {![info exists minimum] || ($value < $minimum)} {set minimum $value}
            if {![info exists maximum] || ($value > $maximum)} {set maximum $value}
        }
        if {$maximum == $minimum} {
            set ratio 1
        } else {
            set ratio [expr {double($maximum - $minimum)}]
        }
        foreach value $values {
            lappend list [expr {($value - $minimum) / $ratio}]                                                    ;# between 0 and 1
        }
        return [list $minimum $maximum $list]
    }

    proc range {start period minimum maximum values} {                                           ;# return Tcl array compatible list
        set list {}
        set ratio [expr {double($maximum - $minimum)}]
        set instant $start
        foreach value $values {
            lappend list [expr {round($instant)}] [expr {$minimum + ($ratio * $value)}]
            set instant [expr {$instant + $period}]
        }
        return $list
    }

    proc reset {this} {
        arimaAbort $this                                                                       ;# cancel any calculation in progress
        neuralAbort $this
        if {[info exists ($this,bar)]} {
            delete $($this,bar); unset ($this,bar)
        }
        if {[info exists ($this,element,prepared)]} {
            delete $($this,element,prepared); unset ($this,element,prepared)
        }
        if {[info exists ($this,element,fitted)]} {
            delete $($this,element,fitted); unset ($this,element,fitted)
        }
        if {[info exists ($this,element,predicted)]} {
            delete $($this,element,predicted); unset ($this,element,predicted)
        }
        unset -nocomplain ($this,action)
        array unset {} $this,last,*
        array unset {} $this,sourced,*
        array unset {} $this,interpolated,*
        array unset {} $this,aggregated,*
        array unset {} $this,smoothed,*
        array unset {} $this,fitting,*
        array unset {} $this,fitted,*
        array unset {} $this,prepared,*
        array unset {} $this,predicted,*
        # reset all entries to initial values:
        set ($this,interpolate,method)\
            [lindex $(interpolate,methods) [lsearch -exact $(interpolate,methods,codes) $composite::($this,-interpolationmethod)]]
        set ($this,aggregate,by) $composite::($this,-aggregateby)
        set ($this,smooth,degree) $composite::($this,-smoothdegree)
        set ($this,fit,method) [lindex $(fit,methods) [lsearch -exact $(fit,methods,codes) $composite::($this,-fitmethod)]]
        set ($this,fit,usePeriod) [expr {$composite::($this,-fitperiod) ne ""}]
        set ($this,fit,useLongPeriod) [expr {$composite::($this,-fitlongperiod) ne ""}]
        foreach name {source prepared fitted predicted} {
            set ($this,display,$name) 0
        }
        set default $widget::option(button,background)
        $($this,prepare,widget,label,source) configure -background $default
        $($this,prepare,widget,label,prepared) configure -background $default
        $($this,fit,widget,label,fitted) configure -background $default
        $($this,predict,widget,label,predicted) configure -background $default
        if {[info exists ($this,widgets)]} {        ;# when deleting predictor, deleted element invokes reset{} via deletedElement{}
            if {$composite::($this,-fitperiod) eq ""} {
                durationInput::set $($this,fit,widget,period) 0
            } else {
                durationInput::set $($this,fit,widget,period) $composite::($this,-fitperiod)
            }
            if {$composite::($this,-fitlongperiod) eq ""} {
                durationInput::set $($this,fit,widget,longPeriod) 0
            } else {
                durationInput::set $($this,fit,widget,longPeriod) $composite::($this,-fitlongperiod)
            }
            if {$composite::($this,-predictduration) eq ""} {
                durationInput::set $($this,predict,widget,duration) 0
            } else {
                durationInput::set $($this,predict,widget,duration) $composite::($this,-predictduration)
            }
        }
    }

    proc clearFittedPredicted {this} {                                                               ;# not to be used while fitting
        if {[info exists ($this,element,fitted)]} {
            delete $($this,element,fitted); unset ($this,element,fitted)
        }
        if {[info exists ($this,element,predicted)]} {
            delete $($this,element,predicted); unset ($this,element,predicted)
        }
        array unset {} $this,fitted,*
        array unset {} $this,predicted,*
        foreach {minimum maximum} [databaseInstances::cursorsRange] {}                                                 ;# in seconds
        xUpdateAxis $this $minimum $maximum
    }

    proc createControls {this parentPath} {
        set helpColor [mc {use popup menu to change color}]
        pack [frame $parentPath.prepareSeparator -relief sunken -borderwidth 1 -height 2] -fill x -expand 1
        set frame [frame $parentPath.prepareControls]                                                   ;# start of preparation area
        pack $frame -fill x -expand 1
        set ($this,prepare,widget,button) [button $frame.prepare\
            -text [mc Prepare] -padx 2 -pady 0 -compound left -image $(prepare,icon) -command "predictor::prepare $this"\
        ]
        pack $($this,prepare,widget,button) -side left -padx {2 0}
        pack [label $frame.interpolationLabel -font $font::(mediumBold) -text [mc {Interpolation method:}]] -side left -padx {2 0}
        set entry [new comboEntry $frame -font $widget::option(entry,font) -width 0 -editable 0 -list $(interpolate,methods)]
        lappend ($this,widgets) $entry
        set ($this,interpolate,widget,method) $entry
        composite::configure $entry entry -textvariable predictor::($this,interpolate,method)
        composite::configure $entry button -listheight [llength $(interpolate,methods)]
        pack $widget::($entry,path) -side left
        pack [label $frame.aggregateLabel -font $font::(mediumBold) -text [mc {Aggregate by:}]] -side left -padx {2 0}
        for {set index 0} {$index < 100} {incr index} {
            if {$index != 1} {lappend values $index}                                                ;# aggregating by 1 does nothing
        }
        set entry [spinbox $frame.aggregateBy\
            -font $font::(mediumBold) -width 2 -values $values -justify right -textvariable predictor::($this,aggregate,by)\
            -state readonly -validate all -validatecommand {predictor::validateNumberEntry 2 0 99 %P}\
        ]
        set ($this,interpolate,widget,aggregate) $entry
        pack $entry -side left
        pack [label $frame.smoothLabel -font $font::(mediumBold) -text [mc {Smooth degree:}]] -side left -padx {2 0}
        set entry [spinbox $frame.smooth\
            -font $font::(mediumBold) -width 2 -from 0 -to 10 -increment 1 -justify right -state readonly -validate all\
            -textvariable predictor::($this,smooth,degree) -validatecommand {predictor::validateNumberEntry 2 0 10 %P}\
        ]
        set ($this,interpolate,widget,smooth) $entry
        pack $entry -side left
        set frame [frame $parentPath.prepareStatus]
        pack $frame -fill x -expand 1
        pack [label $frame.label -font $font::(mediumBold) -text [mc State:]] -side left -padx {2 0}
        set ($this,prepare,widget,status) [text $frame.text -width 0\
            -height 1 -font $font::(mediumNormal) -state disabled -borderwidth 0 -highlightthickness 0 -cursor left_ptr\
        ]
        pack $($this,prepare,widget,status) -side left -padx {2 0} -fill x -expand 1
        set label [label $frame.labelSource -font $font::(mediumBold) -text [mc Original]]      ;# source display and color controls
        lappend ($this,widgets) [new widgetTip -path $label -text $helpColor]
        set menu [viewer::createColorsMenu $label "predictor::colorElement $this source $label %c"]
        bind $label <ButtonPress-3> "if {\[$label cget -state\] ne \"disabled\"} {tk_popup $menu %X %Y}"
        set ($this,prepare,widget,label,source) $label
        pack $label -side right -padx {0 2}
        set ($this,prepare,widget,toggle,source) [checkbutton $frame.toggleSource -highlightthickness 0 -padx 0\
            -variable predictor::($this,display,source) -command "predictor::displayElement $this source"\
        ]
        lappend ($this,widgets)\
            [new widgetTip -path $($this,prepare,widget,toggle,source) -text [mc {toggle display of original (source) data graph}]]
        pack $($this,prepare,widget,toggle,source) -side right -padx {10 0}  ;# enough space for adjacent buttons not to be confused
        set label [label $frame.labelPrepared -font $font::(mediumBold) -text [mc Prepared]]  ;# prepared display and color controls
        lappend ($this,widgets) [new widgetTip -path $label -text $helpColor]
        set menu [viewer::createColorsMenu $label "predictor::colorElement $this prepared $label %c"]
        bind $label <ButtonPress-3> "if {\[$label cget -state\] ne \"disabled\"} {tk_popup $menu %X %Y}"
        set ($this,prepare,widget,label,prepared) $label
        pack $label -side right
        set ($this,prepare,widget,toggle,prepared) [checkbutton $frame.togglePrepared -highlightthickness 0 -padx 0\
            -variable predictor::($this,display,prepared) -command "predictor::displayElement $this prepared"\
        ]
        lappend ($this,widgets)\
            [new widgetTip -path $($this,prepare,widget,toggle,prepared) -text [mc {toggle display of prepared data graph}]]
        pack $($this,prepare,widget,toggle,prepared) -side right                                          ;# end of preparation area
        pack [frame $parentPath.fitSeparator -relief sunken -borderwidth 1 -height 2] -fill x -expand 1
        set frame [frame $parentPath.fitControls]                                                               ;# start of fit area
        pack $frame -fill x -expand 1
        set ($this,fit,widget,button) [button $frame.fit -padx 2 -pady 0 -compound left]
        pack $($this,fit,widget,button) -side left -padx 2
        pack [label $frame.methodLabel -font $font::(mediumBold) -text [mc Method:]] -side left -padx {2 0}
        set entry [new comboEntry $frame\
            -font $widget::option(entry,font) -width 0 -editable 0 -list $(fit,methods) -command "predictor::methodSelected $this"\
        ]
        lappend ($this,widgets) $entry
        set ($this,fit,widget,method) $entry
        composite::configure $entry entry -textvariable predictor::($this,fit,method)
        composite::configure $entry button -listheight [llength $(fit,methods)]
        pack $widget::($entry,path) -side left
        set frame [frame $parentPath.fitPeriods]
        pack $frame -fill x -expand 1
        grid columnconfigure $frame 2 -weight 1
        set ($this,fit,widget,usePeriod) [checkbutton $frame.periodButton\
            -font $font::(mediumBold) -text [mc Periodic:] -variable predictor::($this,fit,usePeriod)\
            -command "predictor::refreshControls $this"\
        ]
        lappend ($this,widgets) [new widgetTip\
            -path $($this,fit,widget,usePeriod) -text [mc {may be 0 for ARIMA method (see help)}]\
        ]
        grid $($this,fit,widget,usePeriod) -row 0 -column 0 -sticky w
        set object [new durationInput $frame -state disabled]
        lappend ($this,widgets) $object
        set ($this,fit,widget,period) $object
        grid $widget::($object,path) -row 0 -column 1 -sticky w
        set ($this,fit,widget,findPeriod)\
            [button $frame.find -padx 0 -pady 0 -image $(fit,icon,findPeriod) -command "predictor::findPeriod $this"]
        lappend ($this,widgets) [new widgetTip -path $($this,fit,widget,findPeriod) -text [mc {estimate the main period}]]
        grid $($this,fit,widget,findPeriod) -row 0 -column 2 -padx 4 -sticky w
        set ($this,fit,widget,useLongPeriod) [checkbutton $frame.longPeriodButton\
            -font $font::(mediumBold) -text [mc {Long period:}] -variable predictor::($this,fit,useLongPeriod)\
            -command "predictor::refreshControls $this"\
        ]
        lappend ($this,widgets) [new widgetTip\
            -path $($this,fit,widget,useLongPeriod) -text [mc {mostly used by neural method (see help)}]\
        ]
        grid $($this,fit,widget,useLongPeriod) -row 1 -column 0 -sticky w
        set object [new durationInput $frame -state disabled]
        lappend ($this,widgets) $object
        set ($this,fit,widget,longPeriod) $object
        grid $widget::($object,path) -row 1 -column 1 -sticky w
        set frame [frame $parentPath.fitStatus]
        pack $frame -fill x -expand 1
        pack [label $frame.label -font $font::(mediumBold) -text [mc State:]] -side left -padx {2 0}
        set ($this,fit,widget,status) [label $frame.status -font $font::(mediumNormal)]
        pack $($this,fit,widget,status) -side left -padx {2 0}
        set label [label $frame.labelFitted -font $font::(mediumBold) -text [mc Residuals]]     ;# fitted display and color controls
        lappend ($this,widgets) [new widgetTip -path $label -text $helpColor]
        set menu [viewer::createColorsMenu $label "predictor::colorElement $this fitted $label %c"]
        bind $label <ButtonPress-3> "if {\[$label cget -state\] ne \"disabled\"} {tk_popup $menu %X %Y}"
        set ($this,fit,widget,label,fitted) $label
        pack $label -side right -padx {0 2}
        set ($this,fit,widget,toggle,fitted) [checkbutton $frame.toggleFitted -highlightthickness 0 -padx 0\
            -variable predictor::($this,display,fitted) -command "predictor::displayElement $this fitted"\
        ]
        lappend ($this,widgets) [new widgetTip -path $($this,fit,widget,toggle,fitted)\
            -text [mc {toggle display of fitting errors (residuals) data graph}]\
        ]
        pack $($this,fit,widget,toggle,fitted) -side right                                                        ;# end of fit area
        pack [frame $parentPath.predictSeparator -relief sunken -borderwidth 1 -height 2] -fill x -expand 1
        set frame [frame $parentPath.predictControls]                                                       ;# start of predict area
        pack $frame -fill x -expand 1
        set ($this,predict,widget,button) [button $frame.predict\
            -text [mc Predict] -padx 2 -pady 0 -compound left -image $(predict,icon) -command "predictor::predict $this"\
        ]
        pack $($this,predict,widget,button) -side left -padx {2 0}
        pack [label $frame.label -font $font::(mediumBold) -text [mc Duration:]] -side left -padx {2 0}
        set object [new durationInput $frame]
        lappend ($this,widgets) $object
        set ($this,predict,widget,duration) $object
        pack $widget::($object,path) -side left
        set frame [frame $parentPath.predictStatus]
        pack $frame -fill x -expand 1
        pack [label $frame.label -font $font::(mediumBold) -text [mc State:]] -side left -padx {2 0}
        set ($this,predict,widget,status) [label $frame.status -font $font::(mediumNormal)]
        pack $($this,predict,widget,status) -side left -padx {2 0}
        set label [label $frame.labelPredicted -font $font::(mediumBold) -text [mc Predicted]] ;# predict display and color controls
        lappend ($this,widgets) [new widgetTip -path $label -text $helpColor]
        set menu [viewer::createColorsMenu $label "predictor::colorElement $this predicted $label %c"]
        bind $label <ButtonPress-3> "if {\[$label cget -state\] ne \"disabled\"} {tk_popup $menu %X %Y}"
        set ($this,predict,widget,label,predicted) $label
        pack $label -side right -padx {0 2}
        set ($this,predict,widget,toggle,predicted) [checkbutton $frame.togglePredicted -highlightthickness 0 -padx 0\
            -variable predictor::($this,display,predicted) -command "predictor::displayElement $this predicted"\
        ]
        lappend ($this,widgets)\
            [new widgetTip -path $($this,predict,widget,toggle,predicted) -text [mc {toggle display of predicted data graph}]]
        pack $($this,predict,widget,toggle,predicted) -side right                                             ;# end of predict area
    }

    proc validateNumberEntry {width minimum maximum value} {
        # note: empty string must be allowed to allow keyboard deletion, replacement, ...
        set valid [expr {\
            ($value eq "") || (\
                ([string length $value] <= $width) && [check31BitUnsignedInteger $value] &&\
                ($value >= $minimum) && ($value <= $maximum)\
            )\
        }]
        if {!$valid} bell
        return $valid
    }

    proc refreshControls {this} {
        # reset everything first:
        $($this,prepare,widget,button) configure -state disabled
        composite::configure $($this,interpolate,widget,method) -state disabled
        $($this,prepare,widget,toggle,source) configure -state disabled
        $($this,prepare,widget,label,source) configure -state disabled
        $($this,prepare,widget,toggle,prepared) configure -state disabled
        $($this,prepare,widget,label,prepared) configure -state disabled
        $($this,interpolate,widget,aggregate) configure -state disabled
        $($this,interpolate,widget,smooth) configure -state disabled
        $($this,fit,widget,button) configure -text [mc Fit] -image $(fit,icon) -command "predictor::fit $this" -state disabled
        composite::configure $($this,fit,widget,method) -state disabled
        $($this,fit,widget,usePeriod) configure -state disabled
        composite::configure $($this,fit,widget,period) -state disabled
        $($this,fit,widget,findPeriod) configure -state disabled
        $($this,fit,widget,useLongPeriod) configure -state disabled
        composite::configure $($this,fit,widget,longPeriod) -state disabled
        $($this,fit,widget,toggle,fitted) configure -state disabled
        $($this,fit,widget,label,fitted) configure -state disabled
        $($this,predict,widget,button) configure -state disabled
        composite::configure $($this,predict,widget,duration) -state disabled
        $($this,predict,widget,toggle,predicted) configure -state disabled
        $($this,predict,widget,label,predicted) configure -state disabled
        if {[info exists ($this,action)]} {                                                               ;# calculation in progress
            switch $($this,action) {
                interpolating {
                    setTextWidgetText $($this,prepare,widget,status) [mc interpolating...]
                }
                aggregating {
                    setTextWidgetText $($this,prepare,widget,status) [mc aggregating...]
                }
                smoothing {
                    setTextWidgetText $($this,prepare,widget,status) [mc smoothing...]
                }
                findingPeriod {
                    $($this,fit,widget,status) configure -text [mc {finding main period...}]
                }
                fitting {
                    set method [lindex $(fit,methods) [lsearch -exact $(fit,methods,codes) $($this,fitting,method,code)]]
                    $($this,fit,widget,status) configure -text [format [mc {%s fitting...}] $method]
                    $($this,fit,widget,button) configure -text [mc Abort] -image $(fit,icon,abort)\
                        -command "predictor::fittingAbort $this" -state normal
                }
                fittingModel {
                    set method [lindex $(fit,methods) [lsearch -exact $(fit,methods,codes) $($this,fitting,method,code)]]
                    $($this,fit,widget,status) configure\
                        -text [formattedParameters [mc {%s fitting...}] $method $($this,fitting,parameters)]
                }
                predicting {
                    $($this,predict,widget,status) configure -text [mc predicting...]
                }
                default {error "invalid action: $($this,action)"}
            }
            return                                                                                    ;# keep everything else locked
        }
        $($this,prepare,widget,toggle,source) configure -state normal
        $($this,prepare,widget,label,source) configure -state normal
        setTextWidgetText $($this,prepare,widget,status) [mc {no source data}]
        $($this,fit,widget,status) configure -text [mc {not fitted}]
        $($this,predict,widget,status) configure -text [mc {not predicted}]
        set preparedText [mc {prepared from %s source points (%s missing) to %s points}]
        if {[info exists ($this,sourced,values)]} {
            $($this,prepare,widget,button) configure -state normal
            composite::configure $($this,interpolate,widget,method) -state normal
            setTextWidgetText $($this,prepare,widget,status)\
                [format [mc {not prepared, %s source points (%s missing)}] $($this,sourced,points) $($this,sourced,missing)]\
                [list $($this,sourced,points) $($this,sourced,missing)]
            $($this,interpolate,widget,aggregate) configure -state normal
            $($this,interpolate,widget,smooth) configure -state normal
        }
        set prepared 0
        if {[info exists ($this,interpolated,values)]} {
            set prepared 1
            set length [llength $($this,interpolated,values)]
            setTextWidgetText $($this,prepare,widget,status) [format $preparedText\
                $($this,sourced,points) $($this,sourced,missing) $length\
            ] [list $($this,sourced,points) $($this,sourced,missing) $length]
            $($this,fit,widget,button) configure -state normal
            composite::configure $($this,fit,widget,method) -state normal
            $($this,fit,widget,usePeriod) configure -state normal
            if {$($this,fit,usePeriod)} {
                composite::configure $($this,fit,widget,period) -state normal
                set index [lsearch -exact $(fit,methods) $($this,fit,method)]
                if {[lindex $(fit,methods,codes) $index] ne "ARIMA"} {                        ;# ARIMA does not support long periods
                    $($this,fit,widget,useLongPeriod) configure -state normal
                    if {$($this,fit,useLongPeriod)} {
                        composite::configure $($this,fit,widget,longPeriod) -state normal
                    }
                } else {
                    set ($this,fit,useLongPeriod) 0
                }
            } else {
                set ($this,fit,useLongPeriod) 0
            }
        }
        if {[info exists ($this,aggregated,values)]} {
            set prepared 1
            set length [llength $($this,aggregated,values)]
            setTextWidgetText $($this,prepare,widget,status) [format $preparedText\
                $($this,sourced,points) $($this,sourced,missing) $length\
            ] [list $($this,sourced,points) $($this,sourced,missing) $length]
        }
        if {$prepared} {
            $($this,prepare,widget,toggle,prepared) configure -state normal
            $($this,prepare,widget,label,prepared) configure -state normal
            $($this,fit,widget,findPeriod) configure -state normal
        }
        if {[info exists ($this,fitted,values)]} {
            if {[llength $($this,fitted,values)] == 0} {
                $($this,fit,widget,status) configure\
                    -text [formattedParameters [mc {error fitting with %s}] $($this,fitted,method) $($this,fitted,parameters)]
            } else {
                $($this,fit,widget,toggle,fitted) configure -state normal
                $($this,fit,widget,label,fitted) configure -state normal
                $($this,fit,widget,status) configure -text [formattedParameters\
                    [mc {best fitted with %s in %s}] $($this,fitted,method) $($this,fitted,parameters) $($this,fitted,seconds)\
                ]
            }
            $($this,predict,widget,button) configure -state normal
            composite::configure $($this,predict,widget,duration) -state normal
        }
        if {[info exists ($this,predicted,values)]} {
            $($this,predict,widget,toggle,predicted) configure -state normal
            $($this,predict,widget,label,predicted) configure -state normal
            if {[llength $($this,predicted,values)] == 0} {
                $($this,predict,widget,status) configure -text [formattedParameters\
                    [mc {error predicting with %s (see trace)}] $($this,predicted,method) $($this,predicted,parameters)\
                ]
            } else {
                $($this,predict,widget,status) configure\
                    -text [formattedParameters [mc {predicted with %s}] $($this,predicted,method) $($this,predicted,parameters)]
            }
        }
        ::update idletasks                                                              ;# make sure changes are immediately visible
    }

    proc updateElement {this} {
        set element [lindex $blt2DViewer::($this,elements) 0]
        if {$element eq ""} return
        switched::configure $element -hide [expr {!$($this,displaySource)}]
        refreshElements $this
    }

    proc formattedParameters {text method parameters {seconds {}}} {           ;# for display after fitting or predicting completion
        foreach parameter $parameters {
            if {$parameter eq ""} continue                                             ;# ignore empty (such as periodic) parameters
            if {[info exists string]} {append string ,}
            append string $parameter
        }
        if {[info exists string]} {
            append method ($string)
        }
        if {$seconds ne ""} {
            set hours [expr {$seconds / 3600}]
            if {$hours == 1} {set unit(hours) [mc hour]} else {set unit(hours) [mc hours]}
            if {$hours == 0} {
                set minutes [expr {$seconds / 60}]
            } else {                                                        ;# do not show the seconds but simply the nearest minute
                set minutes [expr {round(($seconds % 3600) / 60.0)}]
            }
            if {$minutes == 1} {set unit(minutes) [mc minute]} else {set unit(minutes) [mc minutes]}
            set seconds [expr {$seconds % 60}]
            if {$seconds == 1} {set unit(seconds) [mc second]} else {set unit(seconds) [mc seconds]}
            if {$hours == 0} {
                if {$minutes == 0} {
                    set duration [format [mc {%u %s}] $seconds $unit(seconds)]
                } elseif {$seconds == 0} {
                    set duration [format [mc {%u %s}] $minutes $unit(minutes)]
                } else {
                    set duration [format [mc {%u %s and %u %s}] $minutes $unit(minutes) $seconds $unit(seconds)]
                }
            } elseif {$minutes == 0} {
                set duration [format [mc {%u %s}] $hours $unit(hours)]
            } else {
                set duration [format [mc {%u %s and %u %s}] $hours $unit(hours) $minutes $unit(minutes)]
            }
            return [format $text $method $duration]
        } else {
            return [format $text $method]
        }
    }

    proc setTextWidgetText {path text {highlighted {}}} {
        $path configure -state normal
        $path tag delete bold; $path tag configure bold -font $font::(mediumBold)                    ;# remove existing highlighting
        $path delete 1.0 end
        $path insert end $text
        if {[llength $highlighted] > 0} {
            set index 1.0
            foreach string $highlighted {
                set index [$path search -exact -count count $string $index]
                $path tag add bold $index $index+${count}c
            }
        }
        $path configure -state disabled
    }

    proc displayElement {this name} {
        if {$name eq "source"} {
            set element [lindex $blt2DViewer::($this,elements) 0]
            if {$element eq ""} return
        } elseif {[catch {set element $($this,element,$name)}]} return
        switched::configure $element -hide [expr {!$($this,display,$name)}]
        refreshElements $this
    }

    proc colorElement {this name label color} {
        $label configure -background $color
        if {$name eq "source"} {
            set element [lindex $blt2DViewer::($this,elements) 0]
            if {$element eq ""} return
            composite::configure $blt2DViewer::($this,legend,$element) -color $color                ;# synchronize upper layer label
        } elseif {[catch {set element $($this,element,$name)}]} return
        switched::configure $element -color $color
    }

    proc elementColorSet {this element color} {           ;# so we know source element color has been changed from upper layer label
        if {![info exists ($this,prepare,widget,label,source)]} return
        $($this,prepare,widget,label,source) configure -background $color
    }

    proc methodSelected {this name} {
        refreshControls $this
    }

    proc prepare {this} {
        if {$global::rPath eq ""} {
            tk_messageBox -title $(errorTitle) -type ok -icon error\
                -message [mc {R statistics engine undefined (see Tools/Predictor page in Preferences)}]
            return
        }
        if {![info exists ($this,sourced,values)]} return                                                       ;# empty source data
        set intervals [expr {$($this,sourced,points) - 1}]
        if {$intervals < 2} {
            tk_messageBox -title $(errorTitle) -type ok -icon error -message [mc {insufficient number of data points}]
            return
        }
        set element [lindex $blt2DViewer::($this,elements) 0]                                                          ;# must exist
        set label $blt2DViewer::($this,label,$element)
        unset -nocomplain ($this,action) ($this,prepared,start) ($this,prepared,period)\
            ($this,prepared,minimum) ($this,prepared,maximum) ($this,prepared,normalized)                              ;# reset data
        unset -nocomplain start period values                                                                     ;# just to be safe
        set changed 0
        if {![info exists ($this,last,interpolate,method)] || ($($this,interpolate,method) ne $($this,last,interpolate,method))} {
            array unset {} $this,interpolated,*                                                                        ;# reset data
            array unset {} $this,aggregated,*                                           ;# aggregated data if any is no longer valid
            set ($this,action) interpolating; refreshControls $this
            busy 1 .
            set index [lsearch -exact $(interpolate,methods) $($this,interpolate,method)]
            foreach {method start period values} [interpolate\
                [lindex $(interpolate,methods,codes) $index] $intervals $($this,sourced,instants) $($this,sourced,normalized)\
            ] {}
            busy 0 .
            if {[llength $values] == 0} {                                                                    ;# interpolation failed
                unset -nocomplain start period values ($this,last,interpolate,method)                  ;# force recalculations later
            } else {
                set method [lindex $(interpolate,methods) [lsearch -exact $(interpolate,methods,codes) $method]]        ;# translate
                set ($this,interpolate,method) $method            ;# update method which may have been adjusted during interpolation
                set ($this,interpolated,method) $method
                set ($this,interpolated,start) $start
                set ($this,interpolated,period) $period
                set ($this,interpolated,values) $values
                set ($this,last,interpolate,method) $method
                unset -nocomplain ($this,last,aggregate,by) ($this,last,smooth,degree)           ;# force next stages recalculations
            }
            set changed 1
            unset ($this,action); refreshControls $this
        } else {                                                                                           ;# no need to recalculate
            set start $($this,interpolated,start)
            set period $($this,interpolated,period)
            set values $($this,interpolated,values)
        }
        if {![info exists ($this,last,aggregate,by)] || ($($this,aggregate,by) != $($this,last,aggregate,by))} {
            array unset {} $this,aggregated,*
            if {![info exists ($this,interpolated,values)]} {                                             ;# no aggregation possible
                unset -nocomplain ($this,last,aggregate,by)                                            ;# force recalculations later
            } elseif {$($this,aggregate,by) <= 1} {                                                       ;# no aggregation possible
                set ($this,last,aggregate,by) $($this,aggregate,by)
                unset -nocomplain ($this,last,smooth,degree)              ;# force next stage recalculation since there was a change
            } else {
                set ($this,action) aggregating; refreshControls $this
                busy 1 .
                foreach {start period values} [aggregate\
                    $($this,aggregate,by) $($this,interpolated,start) $($this,interpolated,period) $($this,interpolated,values)\
                ] {}
                busy 0 .
                if {[llength $values] == 0} {                                                                  ;# aggregation failed
                    unset -nocomplain start period values ($this,last,aggregate,by)                    ;# force recalculations later
                } else {
                    set ($this,aggregated,by) $($this,aggregate,by)
                    set ($this,aggregated,start) $start
                    set ($this,aggregated,period) $period
                    set ($this,aggregated,values) $values
                    set ($this,last,aggregate,by) $($this,aggregate,by)
                    unset -nocomplain ($this,last,smooth,degree)                                 ;# force next stages recalculations
                }
                set changed 1
                unset ($this,action); refreshControls $this
            }
        } elseif {[info exists ($this,aggregated,values)]} {                                                ;# previously calculated
            set start $($this,aggregated,start)
            set period $($this,aggregated,period)
            set values $($this,aggregated,values)
        }
        if {![info exists ($this,last,smooth,degree)] || ($($this,smooth,degree) != $($this,last,smooth,degree))} {
            array unset {} $this,smoothed,*
            if {![info exists ($this,interpolated,values)]} {                                               ;# no smoothing possible
                unset -nocomplain ($this,last,smooth,degree)                                           ;# force recalculations later
            } elseif {$($this,smooth,degree) == 0} {                                                                 ;# no smoothing
                set ($this,last,smooth,degree) $($this,smooth,degree)
            } else {
                set ($this,action) smoothing; refreshControls $this
                busy 1 .
                if {[info exists ($this,aggregated,values)]} {
                    foreach {start period values} [smooth $($this,smooth,degree)\
                        $($this,aggregated,start) $($this,aggregated,period) $($this,aggregated,values)\
                    ] {}
                } else {
                    foreach {start period values} [smooth $($this,smooth,degree)\
                        $($this,interpolated,start) $($this,interpolated,period) $($this,interpolated,values)\
                    ] {}
                }
                busy 0 .
                if {[llength $values] == 0} {                                                                  ;# aggregation failed
                    unset -nocomplain start period values ($this,last,smooth,degree)                   ;# force recalculations later
                } else {
                    set ($this,smoothed,degree) $($this,smooth,degree)
                    set ($this,smoothed,start) $start
                    set ($this,smoothed,period) $period
                    set ($this,smoothed,values) $values
                    set ($this,last,smooth,degree) $($this,smooth,degree)
                }
                set changed 1
                unset ($this,action); refreshControls $this
            }
        } elseif {[info exists ($this,smoothed,values)]} {                                                  ;# previously calculated
            set start $($this,smoothed,start)
            set period $($this,smoothed,period)
            set values $($this,smoothed,values)
        }
        if {[info exists values]} {                                                                         ;# preparation succeeded
            set ($this,prepared,start) $start
            set ($this,prepared,period) $period
            # normalize values for more precision (in case there were extreme or outlier values before preparation) and reliable fit
            foreach [list minimum maximum ($this,prepared,normalized)] [normalize $values] {}
            # remember limits for later reverse transformation
            set ratio [expr {double($($this,sourced,maximum) - $($this,sourced,minimum))}]
            set ($this,prepared,minimum) [expr {$($this,sourced,minimum) + ($ratio * $minimum)}]
            set ($this,prepared,maximum) [expr {$($this,sourced,minimum) + ($ratio * $maximum)}]
            if {![info exists ($this,element,prepared)]} {
                set color $composite::($this,-preparedcolor)
                if {$color eq ""} {set color $(defaultElementColor)}
                set ($this,element,prepared) [new element $($this,graphPath) -color $color]
                $($this,prepare,widget,label,prepared) configure -background $color
            }
            element::range $($this,element,prepared)\
                [range $start $period $($this,prepared,minimum) $($this,prepared,maximum) $($this,prepared,normalized)]
            element::raise $($this,element,prepared)
            set ($this,display,prepared) 1
        } else {
            if {[info exists ($this,element,prepared)]} {
                element::range $($this,element,prepared) {}
            }
            set ($this,display,prepared) 0
        }
        if {$changed} {
            clearFittedPredicted $this
            refreshControls $this
        }
        refreshElements $this
    }

    proc fit {this} {
        set intervals {}
        set longIntervals {}
        set index [lsearch -exact $(fit,methods) $($this,fit,method)]
        set method [lindex $(fit,methods,codes) $index]
        if {$($this,fit,usePeriod)} {
            set length [llength $($this,prepared,normalized)]
            if {![durationInput::valid $($this,fit,widget,period)]} {
                tk_messageBox -title $(errorTitle) -type ok -icon error -message [mc {invalid fit period}]
                return
            }
            set intervals [expr {round([durationInput::get $($this,fit,widget,period)] / $($this,prepared,period))}]
            if {($method eq "neural") && ($intervals == 0)} {
                tk_messageBox -title $(errorTitle) -type ok -icon error\
                    -message [mc {neural method does not support a zero period}]
                return
            }
            if {$intervals >= ($length / 2)} {
                # in the case of neural fitting, for example, the remainder of data samples once the samples corresponding to the
                # maximum lag have been removed should be greater than the maximum lag for prediction to be possible, hence
                # validate the period and the long period defining the maximum lag.
                tk_messageBox -title $(errorTitle) -type ok -icon error\
                    -message [mc {fit period is too long with respect to the duration of the prepared data}]
                return
            }
            # note: in ARIMA case, intervals can be zero if period is unknown to the user
            if {$($this,fit,useLongPeriod)} {
                if {![durationInput::valid $($this,fit,widget,longPeriod)]} {
                    tk_messageBox -title $(errorTitle) -type ok -icon error -message [mc {invalid long fit period}]
                    return
                }
                set longIntervals [expr {round([durationInput::get $($this,fit,widget,longPeriod)] / $($this,prepared,period))}]
                if {$longIntervals <= $intervals} {
                    tk_messageBox -title $(errorTitle) -type ok -icon error\
                        -message [mc {long fit period must be larger than fit period}]
                    return
                }
                if {$longIntervals >= ($length / 2)} {
                    tk_messageBox -title $(errorTitle) -type ok -icon error\
                        -message [mc {long fit period is too long with respect to the duration of the prepared data}]
                    return
                }
            }
        }
        # note: do not reset costly fitted data (if it exists) in order not to loose it if user aborts fitting process
        array unset {} $this,fitting,*
        set ($this,action) fitting
        set ($this,fitting,seconds) [clock seconds]
        switch $method {
            ARIMA {
                set ($this,fitting,method,code) ARIMA
                refreshControls $this
                fitting $this 0
                arimaFit $this $($this,prepared,normalized) [expr {$longIntervals ne ""? $longIntervals: $intervals}]\
                    "predictor::fitted $this $($this,prepared,period)" "predictor::fitting $this"
            }
            neural {
                set ($this,fitting,method,code) neural
                refreshControls $this
                fitting $this 0
                neuralFit $this $($this,prepared,normalized) $intervals $longIntervals\
                    "predictor::fitted $this $($this,prepared,period)" "predictor::fitting $this"
            }
            default {                                                                                                   ;# automatic
                set ($this,fitting,done) 0
                set ($this,fitting,method,code) ARIMA
                refreshControls $this
                fitting $this 0
                arimaFit $this $($this,prepared,normalized) [expr {$longIntervals ne ""? $longIntervals: $intervals}]\
                    "predictor::fittingStage $this 0" "predictor::fitting $this"
                vwait predictor::($this,fitting,done)
                if {![info exists ($this,action)]} return                                      ;# user aborted (see abort procedure)
                set ($this,fitting,method,code) neural
                refreshControls $this
                fitting $this 0
                neuralFit $this $($this,prepared,normalized) $intervals $longIntervals\
                    "predictor::fittingStage $this 1" "predictor::fitting $this"
                vwait predictor::($this,fitting,done)
                if {![info exists ($this,action)]} return                                      ;# user aborted (see abort procedure)
                fitted $this $($this,prepared,period) $($this,fitting,best,values)\
                    $($this,fitting,best,method) $($this,fitting,best,parameters) $($this,fitting,best,criterion)
                array unset {} $this,fitting,best,*
            }
        }
    }

    proc fittingStage {this end values method parameters criterion} {
        if {![info exists ($this,fitting,best,criterion)] || ($criterion < $($this,fitting,best,criterion))} {
            set ($this,fitting,best,criterion) $criterion
            set ($this,fitting,best,values) $values
            set ($this,fitting,best,method) $method
            set ($this,fitting,best,parameters) $parameters
        }
        set ($this,fitting,done) $end
    }

    proc fitting {this progress} {
        if {![info exists ($this,bar)]} {
            set ($this,bar) [new progressBar [winfo parent $($this,fit,widget,status)]]
            pack $widget::($($this,bar),path) -fill x -padx {2 0}
        }
        composite::configure $($this,bar) -percent [expr {round($progress * 100)}]
        ::update idletasks                                       ;# make sure progress is visible even if computer is heavily loaded
    }

    proc fittingAbort {this} {
        arimaAbort $this
        neuralAbort $this
        if {[info exists ($this,bar)]} {
            delete $($this,bar); unset ($this,bar)
        }
        unset ($this,action)                                                                                     ;# to flag abortion
        set ($this,fitting,done) 1                                                                  ;# allow waiting code to execute
        array unset {} $this,fitting,*
        refreshControls $this
    }

    proc terminate {} {                           ;# public procedure for last resort cleanup (when exiting application for example)
        foreach predictor $(list) {
            arimaAbort $predictor                                                              ;# cancel any calculation in progress
            neuralAbort $predictor
        }
    }

    proc fitted {this period values method parameters criterion} {                                            ;# method in code form
        if {[llength $values] > 0} {                                                                          ;# there was no errors
            # do not invalidate next stage data: keep it visible for easier comparisons when predicting with different models
            # adjust start according to number of fitted values:
            set start [expr {$($this,prepared,start) + (([llength $($this,prepared,normalized)] - [llength $values]) * $period)}]
            if {![info exists ($this,element,fitted)]} {
                set color $composite::($this,-fittedcolor)
                if {$color eq ""} {set color $(defaultElementColor)}
                set ($this,element,fitted) [new element $($this,graphPath) -color $color]
                $($this,fit,widget,label,fitted) configure -background $color
            }
            # errors (residuals) are obviously centered around 0 abscissa:
            element::range $($this,element,fitted)\
                [range $start $period 0 [expr {double($($this,sourced,maximum) - $($this,sourced,minimum))}] $values]
            element::raise $($this,element,fitted)
            set ($this,display,fitted) 1
            refreshElements $this
        } else {
            set ($this,display,fitted) 0
        }
        if {[info exists ($this,bar)]} {
            delete $($this,bar); unset ($this,bar)
        }
        set method [lindex $(fit,methods) [lsearch -exact $(fit,methods,codes) $method]]                                ;# translate
        set ($this,fitted,method) $method
        set ($this,fitted,seconds) [expr {[clock seconds] - $($this,fitting,seconds)}]
        set ($this,fitted,parameters) $parameters
        # intervals is user defined periods converted to number of intervals, validated in fit{}
        if {$($this,fit,usePeriod)} {
            set ($this,fitted,period) [durationInput::get $($this,fit,widget,period)]
            set ($this,fitted,intervals) [expr {round($($this,fitted,period) / $($this,prepared,period))}]
        } else {
            set ($this,fitted,period) {}
            set ($this,fitted,intervals) {}
        }
        if {$($this,fit,useLongPeriod)} {
            set ($this,fitted,longPeriod) [durationInput::get $($this,fit,widget,longPeriod)]
            set ($this,fitted,longIntervals) [expr {round($($this,fitted,longPeriod) / $($this,prepared,period))}]
        } else {
            set ($this,fitted,longPeriod) {}
            set ($this,fitted,longIntervals) {}
        }
        set ($this,fitted,values) $values
        array unset {} $this,fitting,*
        unset ($this,action); refreshControls $this
    }

    proc fitModel {this method parameters} {                                      ;# method in code form, see fit{} for similar code
        set intervals {}
        set longIntervals {}
        if {$($this,fit,usePeriod)} {
            set intervals [expr {round([durationInput::get $($this,fit,widget,period)] / $($this,prepared,period))}]
            if {$($this,fit,useLongPeriod)} {
                set longIntervals [expr {round([durationInput::get $($this,fit,widget,longPeriod)] / $($this,prepared,period))}]
            }
        }
        array unset {} $this,fitted,*
        array unset {} $this,fitting,*
        set ($this,fitting,method,code) $method
        set ($this,fitting,parameters) $parameters
        set ($this,fitting,seconds) [clock seconds]
        set ($this,action) fittingModel; refreshControls $this
        busy 1 .
        switch $method {
            ARIMA {
                foreach {values criterion} [arimaFitModel\
                    $this $parameters $($this,prepared,normalized) [expr {$longIntervals ne ""? $longIntervals: $intervals}]\
                ] {}
            }
            neural {
                foreach {values criterion}\
                    [neuralFitModel $this $parameters $($this,prepared,normalized) $intervals $longIntervals] {}
            }
        }
        busy 0 .
        fitted $this $($this,prepared,period) $values $method $parameters $criterion
    }

    proc predict {this} {
        if {![durationInput::valid $($this,predict,widget,duration)]} {
            tk_messageBox -title $(errorTitle) -type ok -icon error -message [mc {invalid predict duration}]
            return
        }
        set duration [durationInput::get $($this,predict,widget,duration)]
        if {$duration == 0} {
            tk_messageBox -title $(errorTitle) -type ok -icon error -message [mc {predict duration is too short}]
            return
        }
        set period $($this,prepared,period)
        set points [expr {int(double($duration) / $period) + 1}]      ;# number to predict (rounded up so that there is always some)
        set ($this,action) predicting; refreshControls $this
        set index [lsearch -exact $(fit,methods) $($this,fitted,method)]
        switch [lindex $(fit,methods,codes) $index] {
            ARIMA {
                set ($this,predicted,values)\
                    [arimaPredict $this $points $($this,fitted,parameters) $($this,prepared,normalized) $($this,fitted,intervals)]
            }
            neural {
                set ($this,predicted,values) [neuralPredict\
                    $this $points $($this,fitted,parameters) $($this,prepared,normalized)\
                    $($this,fitted,intervals) $($this,fitted,longIntervals)\
                ]
            }
            default error
        }
        set ($this,predicted,method) $($this,fitted,method)
        set ($this,predicted,parameters) $($this,fitted,parameters)
        if {[llength $($this,predicted,values)] > 0} {                                                        ;# there was no errors
            set intervals [expr {[llength $($this,prepared,normalized)] - 1}]
            xUpdateAxis $this $($this,prepared,start) [expr {$($this,prepared,start) + (($intervals + $points) * $period)}]
            if {![info exists ($this,element,predicted)]} {
                set color $composite::($this,-predictedcolor)
                if {$color eq ""} {set color $(defaultElementColor)}
                set ($this,element,predicted) [new element $($this,graphPath) -color $color]
                $($this,predict,widget,label,predicted) configure -background $color
            }
            element::range $($this,element,predicted) [range\
                [expr {$($this,prepared,start) + ($intervals * $period)}] $period $($this,prepared,minimum)\
                $($this,prepared,maximum) [concat [lindex $($this,prepared,normalized) end] $($this,predicted,values)]\
            ]                                                                     ;# connect predicted points to last prepared point
            element::raise $($this,element,predicted)
            set ($this,display,predicted) 1
            refreshElements $this
        } else {
            set ($this,display,predicted) 0
        }
        unset ($this,action); refreshControls $this
    }

    proc findPeriod {this} {
        set ($this,action) findingPeriod; refreshControls $this
        set value [period $($this,prepared,period) $($this,prepared,normalized)]
        set range [expr {([llength $($this,prepared,normalized)] - 1) * $($this,prepared,period)}]
        if {$value ne "" && ($value < ($range / 2))} {                                         ;# ignore obviously too large periods
            durationInput::set $($this,fit,widget,period) $value
        }
        unset ($this,action); refreshControls $this
   }

}


class predictor {

    class element {

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

            set ($this,start) 0
            set ($this,end) 0
            blt::vector create x$this                                                                          ;# x axis data vector
            blt::vector create y$this                                                                          ;# y axis data vector
            blt::vector create weight$this                                                                    ;# weights data vector
            blt::vector create seconds$this                                                                   ;# source data to plot
            x$this notify whenidle; y$this notify whenidle; weight$this notify whenidle                   ;# try to optimize display
            # use object identifier as element identifier, and handle void values
            $path element create $this -mapx x2 -label {} -xdata x$this -ydata y$this -weight weight$this -styles {{void 0 0}}\
                -pixels 1 -symbol splus                               ;# so that lone valid sample among voids is displayed as a dot
            set ($this,path) $path
            switched::complete $this
        }

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

            blt::vector destroy x$this y$this weight$this seconds$this
            $($this,path) element delete $this
            if {$switched::($this,-deletecommand) ne ""} {
                uplevel #0 $switched::($this,-deletecommand)                                ;# always invoke command at global level
            }
        }

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

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

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

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

        proc append {this seconds value} {
            variable seconds$this
            variable y$this
            variable weight$this

            if {$value eq "?"} {                                                                                   ;# void new value
                if {[seconds$this length] == 0} return                                                ;# wait till first valid value
                seconds$this append [seconds$this index end]                                              ;# double last known value
                y$this append [y$this index end]
                weight$this append 0                                                                  ;# and display with void style
            } else {                                                                                                        ;# valid
                seconds$this append $seconds
                y$this append $value
                weight$this append 1                                                                                 ;# normal style
            }
        }

        proc refresh {this } {
            variable x$this
            variable seconds$this

            if {[seconds$this length] == 0} return
            ::update idletasks                              ;# this actually results in less redundant invocations of this procedure
            set path $($this,path)
            set minimum [$path xaxis cget -min]; set maximum [$path xaxis cget -max]
            if {($minimum eq "") || ($maximum eq "")} return
            set width [$path extents plotwidth]
            if {$width <= 2} return                                                  ;# nothing can be plotted in such a small space
            set pixelsPerSecond [expr {double($width) / ($maximum - $minimum)}]
            $path x2axis configure -min -$width
            x$this expr "round((seconds$this - $maximum) * $pixelsPerSecond)"
        }

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

            seconds$this set {}; y$this set {}; weight$this set {}
            foreach {stamp value} $list {                                        ;# assumes that timestamps are ordered increasingly
                if {![string is integer $stamp]} {                                                ;# handle times already in seconds
                    set stamp [clock scan $stamp]
                }
                if {$value eq ""} {                       ;# void value (instead of ? since data is assumed to come from a database)
                    append $this $stamp ?
                } else {
                    append $this $stamp $value
                }
                if {![info exists start]} {set start $stamp}
            }
            if {![info exists stamp]} {                                                                         ;# no samples at all
                set ($this,start) 0
                set ($this,end) 0
            } else {                                                       ;# remember whole data (even including voids) time limits
                set ($this,start) $start                                                        ;# remember range extents separately
                set ($this,end) $stamp                                         ;# (since null values are not saved in vectors above)
            }
            refresh $this
        }

        proc raise {this} {                                                                           ;# display over other elements
            set list {}
            foreach element [$($this,path) element show] {
                if {$element ne $this} {lappend list $element}                       ;# note: elements may have names not as numbers
            }
            lappend list $this
            $($this,path) element show $list
        }

    }

}
