# 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: graph.tcl,v 2.66 2006/01/28 19:16:59 jfontain Exp $


class bltGraph {                         ;# this is common code for all BLT viewers of graph type (includes stripchart and barchart)

    variable plotBackground black
    set (graphs) {}

    proc bltGraph {this path {graph 0}} {                         ;# graph object if graphing, as opposed to database ranges display
        if {![info exists (menu,grid,label)]} {
            set (menu,grid,label) [mc Grid]
            set (menu,grid,help) [mc {whether a grid is displayed in the plot area}]
        }
        $path configure -plotborderwidth 1 -topmargin 3\
            -bufferelements 0                       ;# to avoid slowdowns when there are many data points (such as in database data)
        $path xaxis configure -tickfont [blt2DViewer::axisTickFont] -title {} -command bltGraph::axisTime\
            -tickshadow {} -showticks 0             ;# delay showing ticks until step size is known to avoid BLT out of range errors
        bind $path <Configure> "+ bltGraph::resized $this"                                                     ;# track size updates
        if {$graph != 0} {
            set ($this,marker) [$path marker create polygon -fill {} -coords {-Inf Inf Inf Inf Inf -Inf -Inf -Inf}]
            $path crosshairs configure -color blue
            lappend (graphs) [set ($this,graph) $graph]          ;# remember graphs in order to be able to globally set some options
        }
        set ($this,path) $path
        set ($this,plotWidth) 0                                                                                  ;# cache plot width
        set ($this,range) 0
        set ($this,crossHairs) 0
    }

    proc ~bltGraph {this} {
        bind $($this,path) <Configure> {}
        if {[info exists ($this,graph)]} {ldelete (graphs) $($this,graph)}
    }

    proc setRange {this value} {
        set ($this,range) $value
    }

    proc xAxisUpdateRange {this {maximum {}}} {                                 ;# maximum time is time at rightmost x axis position
        set range $($this,range)
        if {$range == 0} return                                                                        ;# wait till range is defined
        if {$maximum eq ""} {
            set maximum [$($this,path) xaxis cget -max]                                         ;# can be empty if no data displayed
            if {$maximum eq ""} return
        }
        if {$range < 10} {
            set maximum [expr {$maximum + ((10 - $range) / 2)}]
            set range 10
        }
        $($this,path) xaxis configure -min [expr {$maximum - $range}] -max $maximum
    }

    proc axisTime {path value} {
        set now [clock seconds]
        if {[set minimum [$path xaxis cget -min]] eq ""} {return ?}                                          ;# graph has no minimum
        set minimum [expr {round($minimum)}]                                                    ;# BLT passes a floating point value
        if {[clock format $minimum -format %Y] < [clock format $now -format %Y]} {                         ;# spans more than a year
            set format {%Y-%m-%d }                                                                       ;# show year, month and day
        } elseif {[clock format $minimum -format %j] < [clock format $now -format %j]} {                    ;# spans more than a day
            set format {%m-%d }                                                                                ;# show month and day
        }
        set value [expr {round($value)}]                                                        ;# BLT passes a floating point value
        if {($value % 60) == 0} {
            append format %R
        } else {
            append format %T                                                                     ;# show seconds only when necessary
        }
        return [clock format $value -format $format]
    }

    proc xUpdateGraduations {this} {
        if {($($this,plotWidth) == 0) || ($($this,range) == 0)} return                      ;# plot width or range are not known yet
        # number = $division * ($range / $step)                                                                # number of divisions
        # ($plotWidth / $number) > 2                          # make sure that there is at least 1 pixel between neighbour divisions
        # => $step > ((2 * $division * $range) / $plotWidth)
        # use the maximum division in divisions discrete list
        set minimum [expr {(2.0 * 6 * $($this,range)) / $($this,plotWidth)}]      ;# use floating point to avoid integer overflowing
        # choose among predefined discrete values (in seconds)
        foreach step {10 60 300 600 1800 3600 18000 36000 86400 604800 2419200 31536000} division {5 6 5 5 5 6 5 5 4 7 4 12} {
            if {$step > $minimum} break
        }
        $($this,path) xaxis configure -stepsize $step -subdivisions $division -showticks 1   ;# now that sizes are known, show ticks
    }

    proc resized {this} {
        update idletasks                                                                              ;# make sure sizes are correct
        set width [$($this,path) extents plotwidth]
        if {$width != $($this,plotWidth)} {
            set ($this,plotWidth) $width
            xUpdateGraduations $this                                                      ;# optimize graduations for new plot width
        }
    }

    proc enterPlotArea {this x y} {
        set path $($this,path)
        $path configure -cursor tcross
        $path crosshairs on
        set ($this,crossHairs) 1
        bind $path <Any-Motion> "bltGraph::processMotion $this %x %y"
        lifoLabel::push $global::messenger "[axisTime $path $x] $y"
    }

    proc leavePlotArea {this} {
        set path $($this,path)
        $path configure -cursor {}
        $path crosshairs off
        set ($this,crossHairs) 0
        bind $path <Any-Motion> {}
        lifoLabel::pop $global::messenger
    }

    proc processMotion {this x y} {
        set path $($this,path)
        $path crosshairs configure -position @$x,$y
        foreach {x y} [$path invtransform $x $y] {}
        lifoLabel::pop $global::messenger
        lifoLabel::push $global::messenger "[axisTime $path $x] $y"
    }

    proc hideAxisAndCrossHair {this yesOrNo} {
        set path $($this,path)
        $path xaxis configure -hide $yesOrNo
        $path yaxis configure -hide $yesOrNo
        if {[info exists ($this,marker)]} {
            if {$yesOrNo} {
                $path marker bind $($this,marker) <Enter> {}
                $path marker bind $($this,marker) <Leave> {}
            } else {
                $path marker bind $($this,marker) <Enter> "bltGraph::enterPlotArea $this %x %y"
                $path marker bind $($this,marker) <Leave> "bltGraph::leavePlotArea $this"
            }
        }
    }

    proc allowDrag {this drag} {
        dragSite::provide $drag DATETIME "bltGraph::dragData $this"
        set ($this,validCommand) [switched::cget $drag -validcommand]
        switched::configure $drag -validcommand "bltGraph::validateDrag $this"                                           ;# override
    }

    proc validateDrag {this x y} {
        if {$($this,crossHairs)} {                                                                         ;# for date and time data
            return 1
        } else {                                                                                  ;# use enclosing object validation
            return [eval $($this,validCommand) $x $y]
        }
    }

    proc dragData {this format} {                                                               ;# sole supported fornat is DATETIME
        return [expr {round(\
            [lindex [eval $($this,path) invtransform [split [string trimleft [$($this,path) crosshairs cget -position] @] ,]] 0]\
        )}]                                                                                     ;# BLT passes a floating point value
    }

    proc xRotateLabels {this value} {
        $($this,path) xaxis configure -rotate $value
    }

}
