##
## Copyright 2001 Digi International, jeffa@digi.com
##
package require Widget 2.0
package provide Timegraph 2.0
## Requires Tcl8.3 or higher

##------------------------------------------------------------------------
## PROCEDURE
##	timegraph
##
## DESCRIPTION
##	Implements a timegraph megawidget.  Designed to display real-time 
##	data as a moving line graph.  Like a lie detector or seismograph.
##
## ARGUMENTS
##	timegraph <window pathname> <options>
##
## OPTIONS
##	(Any canvas widget option may be used in addition to these)
##
## -xscale
##	integer multiplier for the horizontal (time) scale. DEFAULT:10
##
## -yscale
##	decimal multiplier for the vertical scale.
##	y(user integer units) * -yscale = vertical distance in pixels
##	If y values are typically larger than the graph height, -yscale 
##	should be set to a value less than 1.
##
## RETURNS: the window pathname
##
## METHODS
##	These are the methods that the Timegraph recognizes.  Aside from
##	those listed here, it accepts what is valid for canvas widgets.
##
## configure ?option? ?value option value ...?
##
## cget option
##	Standard tk widget routines.
##
## plot x y
##	x - linear (incrementing) integer data; usually time.
##	y - integer value associated with x.
##
## NAMESPACE & STATE
##	The megawidget creates a global array with the classname, and a
## global array which is the name of each megawidget is created.  The latter
## array is deleted when the megawidget is destroyed.
##	Public procs of $CLASSNAME and [string tolower $CLASSNAME] are used.
## Other procs that begin with $CLASSNAME are private.  For each widget,
## commands named .$widgetname and $CLASSNAME$widgetname are created.
##
## EXAMPLE USAGE:
##
## pack [timegraph .graph -height 200 -width 300 -background yellow]
## .graph plot 1 50
## .graph plot 2 30
## .graph plot 3 76
##
##------------------------------------------------------------------------

# Create this to make sure they are registered in auto_mkindex
# these must come before the [widget create ...]
proc Timegraph args {}
proc timegraph args {}
widget create Timegraph -type frame -base canvas -components {
    {base canvas canvas { -width 300 -height 100 }}    
} -options {
    {-xscale		xScale		XScale		{10}}
    {-yscale		yScale		YScale		{1.0}}
    {-graphfg		graphFg		GraphFG		{#333399}}
    {-graphbg		graphBg		GraphBG		{#CCCCFF}}
}

namespace eval ::Widget::Timegraph {;

namespace import -force ::Utility::best_match

;proc construct {w} {
    upvar \#0 [namespace current]::$w data

    grid $data(canvas)        
}

;proc init {w} {    
    upvar \#0 [namespace current]::$w data
    set data(width) [uplevel $data(basecmd) cget -width ]
    set data(height) [uplevel $data(basecmd) cget -height ]
    set data(lstScaled) {}
    set data(didfirstpoint) false
    $data(canvas) configure -background $data(-graphbg)
}

;proc configure {w args} {
    upvar \#0 [namespace current]::$w data
    set truth {^(1|yes|true|on)$}
    foreach {key val} $args {
	switch -- $key {
	    -xscale	{
		    if { ![string is double $val] } {
			    return -code error "-xscale value - $val must be a number \(decimal or integer\)"
		    }
	    }
	    -yscale	{
		    if { ![string is double $val] } {
			    return -code error "-yscale value - $val must be a number \(decimal or integer\)"
		    }
	    }
	}
	set data($key) $val
    }
    $data(canvas) configure -background $data(-graphbg)    
}

;proc _plot {w args} {
    upvar \#0 [namespace current]::$w data
    set x [lindex $args 0]
    set y [lindex $args 1]
    set discard false
    set data(yScale) $data(-yscale)
    if { $data(didfirstpoint) } {
	    catch {uplevel $data(basecmd) delete Poly }    
    } else {
        ## This is the first point, fabricate an oldx
        set data(oldx) [expr {$x - 1}]
    }
    set interval [expr {($x - $data(oldx)) * $data(-xscale)}]
    set yScaled [list [expr {round($data(height) - $y * $data(yScale))} ]]
    
    ## Shift all points left by $interval and discard points that fall off
    ## the canvas.
    foreach {xi yi} $data(lstScaled) {
        if { ($xi - $interval) >= 0 } {
    		# It is a point on the canvas
		    lappend lstNew [expr {$xi - $interval}] $yi		
	    } else {
            ## Saving the last point removed so we can put it back on. It will
            ## let the graph scroll off the canvas better if we plot an extra
            ## point that's not visible, but affects the angle of the line.
            set lastX $xi
            set lastY $yi
            set discard true
        }
    }
    if { $discard } {
        ## Prepending the last discarded point
        if {[info exists lstNew]} {
            set lstNew [lreplace $lstNew -2 -1 [expr {$lastX - $interval}] $lastY]
        } else {
            ## All of the points were discarded; lstNew was not appended with points
            set lstNew [list [expr {$lastX - $interval}] $lastY]
        }
    }
    if { $data(didfirstpoint) } {
    	if { $data(oldx) > $x } {
    		# x was reset
    		#set lstNew [list [expr {$data(width) - $interval}] $yScaled]
            set lstNew {}
            set data(didfirstpoint) false
    	}
    	# This will catch instances of trying to plot on a graph that has been destroyed
    	if {[catch { set data(lstScaled) $lstNew} result]} { 
    		return
    	}
    }
    ## Add a point to the right edge
    lappend data(lstScaled) $data(width) $yScaled
    if { $data(didfirstpoint) } {
    	set data(lstPoly) [lreplace $data(lstScaled) 0 0 [lindex $data(lstScaled) 0] $data(height) [lindex $data(lstScaled) 0]]
    	lappend data(lstPoly) $data(width) $data(height)
	    uplevel $data(basecmd) create poly $data(lstPoly) -fill $data(-graphfg) -tags Poly
    	update
    }
    set data(oldx) $x
    set data(didfirstpoint) true
}

}; # end namespace



