GraphColumnView2D.st
author Claus Gittinger <cg@exept.de>
Fri, 15 Jun 2018 10:54:35 +0200
changeset 5816 7876c07931a7
parent 4903 5f16713d82ab
child 4930 7a6e813d8d17
permissions -rw-r--r--
#DOCUMENTATION by cg class: ComboListView class comment/format in: #documentation

"{ Package: 'stx:libwidg2' }"

"{ NameSpace: Smalltalk }"

GraphColumnView subclass:#GraphColumnView2D
	instanceVariableNames:'colorMap gridXoffset gridX gridY actionBlock doubleClickBlock
		buttonReleaseBlock menuAccessBlock'
	classVariableNames:''
	poolDictionaries:''
	category:'Views-Graphs'
!

!GraphColumnView2D class methodsFor:'documentation'!

documentation
"
    The class provides all the functionality for showing, scrolling and manipulating graphs
    described through to a GraphColumn description. Each change in a graph description
    immediately take affect.


    [See also:]
	GraphColumn
	GraphColumnView
	GraphColumnView3D

    [Author:]
	Claus Atzkern
"


! !

!GraphColumnView2D class methodsFor:'defaults'!

gridStep
    ^ 8
! !

!GraphColumnView2D class methodsFor:'examples'!

test0
    "testing references and actions

     start with:
         self test0
    "
    |top col view x|

    top  := StandardSystemView extent:800 @ 400.
    view := GraphColumnView2D origin:0@0 extent:1.0@1.0 in:top.

    top label:'2D-View'.

    col := GraphColumn name:' sqrt(x) '.
    col relativeXaxis:0.5.
    col lineStyle:#solid.

    col hLineStyle:#dashed.
    col hLineList:#( 0 ).
    col scaleY:40.

    col functionYblock:[:start :array|
        x := 0.001.
        1 to:(array size) do:[:i| array at:i put:(x ln - (0.1*x)). x := x + 0.1 ].
        array
    ].

    view showDefaultMenu:true.
    view showGrid:true.
    view columns:{ col }.
    top openAndWait.
!

test1
    "testing references and actions

     start with:
	 self test1
    "
    |top list view x|

    top  := StandardSystemView extent:800 @ 400.
    view := GraphColumnView2D origin:0@0 extent:1.0@1.0 in:top.
    list := OrderedCollection new.

    top label:'2D-View'.

    #( 0.25 0.5 0.75 ) keysAndValuesDo:[:aKey :xAxis||aCol|
	aCol := GraphColumn name:aKey.
	aCol relativeXaxis:xAxis.

	xAxis ~= 0.5 ifTrue:[
	    xAxis < 0.5 ifTrue:[
		aCol foregroundColor:(Color red).
	    ] ifFalse:[
		aCol foregroundColor:(Color blue).
		aCol lineStyle:#dashed
	    ]
	].
	aCol hLineStyle:#dashed.
	aCol hLineList:#( 0 ).
	aCol scaleY:40.

	aCol functionYblock:[:start :array|
	    x := (start - 1) * 0.2.
	    1 to:(array size) do:[:i| array at:i put:(x sin). x := x + 0.2 ].
	    array
	].
	list add:aCol.
    ].

    view action:[:column :indexX :deltaY|
	Transcript showCR:'SINGLE CLICK:'.

	Transcript showCR:' column: ', column printString,
			  ' indexX: ', indexX printString,
			  ' deltaY: ', deltaY printString.

	view referenceAdd:indexX
    ].

    view doubleClickAction:[:column :indexX :deltaY|
	Transcript showCR:'DOUBLE CLICK:'.

	Transcript showCR:' column: ', column printString,
			  ' indexX: ', indexX printString,
			  ' deltaY: ', deltaY printString.
    ].
    view showDefaultMenu:true.
    view showGrid:true.
    view columns:list.
    top openAndWait.



!

test2
    "testing models and change notifications

     start with:
	 self test2
    "
    |top list cols listView graph red blue yellow sav pause time
     label butAct button b1 b2|

    top      := StandardSystemView extent:950 @ 400.
    listView := ListView origin:0.0@20 corner:150@1.0 in:top.
    graph    := GraphColumnView2D origin:150@0 corner:1.0@1.0 in:top.
    listView level:1.
    (Label origin:2@2 in:top) label:'Delay:'.

    time   := 0.1.
    label  := Label origin:82@1 corner:128@19 in:top.
    label sizeFixed:true.
    label label:(time printString).
    label level:1.

    butAct := [:dT| time := (time + dT) max:0.0. label label:(time printString) ].

    button := (ArrowButton leftIn:top)  origin:60@0  extent:20@20.
    button action:[butAct value:-0.05].
    button autoRepeat:true.
    button := (ArrowButton rightIn:top) origin:130@0 extent:20@20.
    button action:[butAct value:0.05].
    button autoRepeat:true.


    cols     := OrderedCollection new.
    list     := List new.

    top label:'Testing 2D-View: Models & Style'.

    listView list:OrderedCollection new.
    graph showGrid:true.
    graph listHolder:list.
    top openAndWait.

    #( 0.2 0.8 ) keysAndValuesDo:[:aKey :xAxis||aCol x|
	aCol := GraphColumn name:'Column <', aKey printString, '>'.
	aCol relativeXaxis:xAxis.
	aCol hLineFgColor:(Color blue).
	aCol scaleY:40.
	aCol hLineStyle:#dashed.

	aCol functionYblock:[:start :array|
	    x := (start - 1) * 0.2.
	    1 to:(array size) do:[:i| array at:i put:(x sin). x := x + 0.2 ].
	    array
	].
	cols add:aCol.
    ].

    red    := Color red.
    blue   := Color blue.
    yellow := Color yellow lightened.

    pause  := [ |l|
	l := listView list.
	l size > 500 ifTrue:[l removeFromIndex:1 toIndex:400. listView list:l].
	listView scrollToBottom.

	(top realized and:[time > 0]) ifTrue:[Delay waitForSeconds:time] ].

    [   [top realized] whileTrue:[
	    listView add:'ADD COLUMNS'.
	    cols do:[:el|
		listView add:( '    ', el printString ).
		list add:el.
		pause value
	    ].

	    listView add:( 'GRAPH' ).
	    listView add:( '    Background' ).
	    sav := graph backgroundColor.
	    graph backgroundColor:yellow.
	    pause value.
	    graph backgroundColor:sav.
	    sav := graph foregroundColor.
	    listView add:( '    Foreground' ).
	    graph foregroundColor:blue.
	    pause value.
	    graph foregroundColor:sav.

	    listView add:( 'GRID' ).
	    listView add:( '    Grid X' ).
	    graph gridExtent:(4 @ 0).
	    pause value.
	    listView add:( '    Grid Y' ).
	    graph gridExtent:(0 @ 4).
	    pause value.
	    listView add:( '    Grid X/Y' ).
	    graph gridExtent:(8 @ 8).
	    pause value.
	    listView add:( '    Color' ).
	    sav := graph gridColor.
	    graph gridColor:yellow.
	    pause value.
	    graph gridColor:sav.
	    listView add:( '    Off' ).
	    graph showGrid:false.
	    pause value.
	    listView add:( '    On' ).
	    graph showGrid:true.

	    listView add:( 'REFERENCES' ).
	    listView add:( '    Add' ).
	    #( 7  15  43  90 ) do:[:i| graph referenceAdd:i. pause value].
	    top realized ifTrue:[Delay waitForSeconds:time].
	    sav := graph referenceColor.
	    listView add:( '    Color' ).
	    graph referenceColor:blue.
	    pause value.
	    graph referenceColor:sav.
	    listView add:( '    Remove' ).
	    #( 7  15  43  90 ) do:[:i| graph referenceRemove:i. pause value ].
	    pause value.

	    listView add:'REMOVE COLUMNS'.
	    [list notEmpty] whileTrue:[
		listView add:( '    ', list removeFirst printString ).
		pause value
	    ].
	].

    ] forkAt:1.


!

test3
    "testing models and change notifications

     start with:
	 self test3
    "
    |top list slices index view graph column actLbl tmOut button bAction tmLbl title pause toggle auto next|

    top   := StandardSystemView extent:800 @ 400.
    view  := View origin:0@0  corner:1.0@28  in:top.
    graph := GraphColumnView2D origin:0@28 corner:1.0@1.0 in:top.
    list  := OrderedCollection new.
    top label:'Testing 2D-View: Model-Column'.

    slices := #( ( foregroundColor       #color )
		 ( lineStyle             #dashed )
		 ( lineWidth             4 )

		 ( hLineFgColor          #color )
		 ( hLineStyle            #dashed )
		 ( hLineWidth            4 )
		 ( hLineList             #( -0.8  -0.5  0  0.5  0.8 ) )

		 ( shown                 false )
		 ( scaleY                10 )
		 ( transY                20 )
		 ( relativeXaxis         0.2 )
	       ).


    column := GraphColumn name:'test'.
    column relativeXaxis:0.5.
    column scaleY:40.
    column hLineList:#( -1   1 ).
    column functionYblock:[:start :array||x|
	x := (start - 1) * 0.2.
	1 to:(array size) do:[:i| array at:i put:(x sin). x := x + 0.2 ].
	array
    ].
    tmOut := 1.0.
    auto  := true.
    index := 1.
    next  := false.

    view level:1.

    toggle := CheckBox origin:0@0.0 corner:100@1.0 in:view.
    toggle label:'Auto/Step'.
    toggle turnOn.
    toggle action:[:aState|
	(next := auto := aState) ifTrue:[
	    title label:'Time: '.
	    tmLbl label:(tmOut printString).
	] ifFalse:[
	    title label:'Step: '.
	    tmLbl label:(index printString).
	]
    ].

    title := Label origin:100@0.0 corner:160@1.0 in:view.
    title adjust:#right.
    title sizeFixed:true.
    title label:'Time: '.

    bAction := [:add|
	auto ifTrue:[
	    tmOut := (tmOut + (add ifTrue:[0.1] ifFalse:[-0.1])) max:0.1.
	    tmLbl label:(tmOut printString).
	] ifFalse:[
	    add ifTrue:[
		(index := index + 1) > slices size ifTrue:[index := 1]
	    ] ifFalse:[
		(index := index - 1) < 1 ifTrue:[index := slices size]
	    ].
	    tmLbl label:(index printString).
	    next := true.
	]
    ].

    button := ArrowButton leftIn:view.
    button origin:160@0.0 corner:180@1.0.
    button autoRepeat:true.
    button pressAction:[ bAction value:false ].

    button := ArrowButton rightIn:view.
    button origin:181@0.0 corner:201@1.0.
    button autoRepeat:true.
    button pressAction:[ bAction value:true ].

    tmLbl := Label origin:202 @0.0 corner:230@1.0 in:view.
    tmLbl adjust:#center.
    tmLbl sizeFixed:true.
    tmLbl label:(tmOut printString).

    actLbl := Label origin:230 @0.0 corner:290@1.0 in:view.
    actLbl sizeFixed:true.
    actLbl adjust:#right.
    actLbl label:'Action: '.

    actLbl := Label origin:290 @0.0 corner:1.0@1.0 in:view.
    actLbl adjust:#left.
    view subViews do:[:v| v verticalInset:2 ].

    graph gridExtent:( 4 @ 4 ).
    graph showDefaultMenu:true.
    graph listHolder:(Array with:column).
    top   openAndWait.

    pause := [
	auto ifTrue:[
	    top realized ifTrue:[Delay waitForSeconds:tmOut]
	] ifFalse:[
	    next := false.

	    [(next not and:[top realized])] whileTrue:[
		Delay waitForSeconds:0.2
	    ]
	]
    ].

    [   |sav rsl wsl arg dsc|

	[top realized] whileTrue:[

	    dsc := slices at:index.

	    auto ifTrue:[
		(index := index + 1) > slices size ifTrue:[
		    index := 1
		]
	    ].
	    rsl := dsc at:1.
	    wsl := (rsl, ':') asSymbol.
	    (arg := dsc at:2) == #color ifTrue:[arg := Color red].
	    sav := column perform:rsl.

	    actLbl label:wsl printString, ' ', arg printString.
	    column perform:wsl with:arg.
	    pause value.
	    column perform:wsl with:sav.
	]

    ] forkAt:1.


!

testRun
    "running view

     start with:
	 self testRun
    "
    |top list view step x offs time lblX lblT lblC cbox halt xOrigin|

    halt := false.
    top  := StandardSystemView extent:800 @ 400.
    view := GraphColumnView2D origin:0@20 extent:1.0@1.0 in:top.
    cbox := CheckBox origin:0@0.0 corner:50@20 in:top.
    cbox label:'Stop'.
    cbox action:[:v| halt := v].
    xOrigin := 1 asValue.

    lblX := Label origin:100@0.0 corner:0.4@20 in:top.
    lblT := Label origin:0.4@0.0 corner:0.7@20 in:top.
    lblC := Label origin:0.7@0.0 corner:1.0@20 in:top.
    lblX level:1. lblX adjust:#left.
    lblT level:1. lblT adjust:#left.
    lblC level:1. lblC adjust:#left.
    offs := 0.
    step := 2.
    list := OrderedCollection new.

    top label:'Testing 2D-View: Performance Test'.

    #( 0.25 0.5 0.75 ) do:[:xAxis||aCol|
	aCol := GraphColumn new.
	aCol relativeXaxis:xAxis.
	xAxis ~= 0.5 ifTrue:[
	    xAxis < 0.5 ifTrue:[aCol foregroundColor:(Color red)]
		       ifFalse:[aCol foregroundColor:(Color blue)]
	].
	aCol hLineStyle:#dashed.
	aCol hLineList:#( 0 ).
	aCol scaleY:40.

	aCol functionYblock:[:start :array|
	    x := start * 0.2.
	    1 to:(array size) do:[:i| array at:i put:(x sin). x := x + 0.2 ].
	    array
	].
	list add:aCol.
    ].
    view showGrid:true.
    view gridExtent:(4 @ 4).
    view columns:list.
    view windowSize:50.
    view scrollUpdatesOriginX:true.
    view graphOriginXHolder:xOrigin.

    top openAndWait.

    [   |tm total inc|
	inc := 0.
	total := 0.

	[top realized] whileTrue:[
	    halt ifFalse:[
		lblX label:('X-Offset:    ', view graphOriginX printString).
		lblT label:('Total Time:    ', (total // 1000) printString, '::', (total \\ 1000) printString ).
		lblC label:('Runs:    ', inc printString).

		tm := Time millisecondsToRun:[
		    xOrigin value:(xOrigin value + step)
		].
		inc := inc + 1.
		total := total + tm.
	   ] ifTrue:[
		Delay waitForSeconds:0.2
	   ]
	]

    ] forkAt:1.
! !

!GraphColumnView2D class methodsFor:'menu'!

defaultMenu
    "this window spec was automatically generated by the ST/X MenuEditor"

    "do not manually edit this - the builder may not be able to
     handle the specification if its corrupted."

    "
     MenuEditor new openOnClass:GraphColumnView2D andSelector:#defaultMenu
     (Menu new fromLiteralArrayEncoding:(GraphColumnView2D defaultMenu)) startUp
    "

    <resource: #menu>

    ^

       #(#Menu

	   #(
	     #(#MenuItem
		#label: 'Show Grid'
		#indication: #showGrid:
	    )
	     #(#MenuItem
		#label: 'Show References'
		#indication: #showReferences:
	    )
	     #(#MenuItem
		#label: 'Grid Extent'
		#enabled: #showGrid
		#submenu:
		 #(#Menu

		     #(
		       #(#MenuItem
			  #label: 'Extent'
			  #submenu:
			   #(#Menu

			       #(
				 #(#MenuItem
				    #label: 'larger'
				    #value: #doGridExtent:
				    #argument: #larger
				)
				 #(#MenuItem
				    #label: 'smaller'
				    #value: #doGridExtent:
				    #argument: #smaller
				)
			      ) nil
			      nil
			  )
		      )
		       #(#MenuItem
			  #label: 'X-Step'
			  #argument: #doGridStepX:
			  #submenuChannel: #gridStepMenuSelector:
		      )
		       #(#MenuItem
			  #label: 'Y-Step'
			  #argument: #doGridStepY:
			  #submenuChannel: #gridStepMenuSelector:
		      )
		    ) nil
		    nil
		)
	    )
	     #(#MenuItem
		#label: 'Zoom Y'
		#submenuChannel: #subMenuZoomY
	    )
	     #(#MenuItem
		#label: 'Print'
		#value: #doPrint
	    )
	  ) nil
	  nil
      )


!

gridStepMenuSelector:aSelector
    |menu width height item bitmap|

    menu   := Menu new.
    width  := 40.
    height := 10.

    #( off 1 2 4 8 ) do:[:aNumberOrSymbol|
	item := MenuItem labeled:(aNumberOrSymbol printString).
	item value:aSelector.
	item argument:aNumberOrSymbol.
	menu addItem:item.
    ].
  ^ menu

"
(self styleMenuSelector:#lineStyle) startUp
"


! !

!GraphColumnView2D methodsFor:'accessing actions'!

action
    "action block which is performed on a single button click.
     Number of arguments to the block can be 0 upto 4.

     argument 1: nearest column to the click point
     argument 2: the logical index (X)
     argument 3: the distance y from the click point to the columns's graph
     argument 4: physical y value
    "
    ^ actionBlock
!

action:aBlockUpTo4Args
    "action block which is performed on a single button click.
     Number of arguments to the block can be 0 upto 4.

     argument 1: nearest column to the click point
     argument 2: the logical index (X)
     argument 3: the distance y from the click point to the columns's graph
     argument 4: physical y value
    "
    actionBlock := aBlockUpTo4Args
!

buttonReleaseBlock
    "action block which is performed if the button is released; the number
     of arguments to the block can be 0 upto 3.

     argument 1: physical x value
     argument 2: physical y value
     argument 3: the logical index (X)
    "
    ^ buttonReleaseBlock
!

buttonReleaseBlock:aThreeArgAction
    "action block which is performed if the button is released; the number
     of arguments to the block can be 0 upto 3.

     argument 1: physical x value
     argument 2: physical x value
     argument 3: the logical index (X)
    "
    buttonReleaseBlock := aThreeArgAction
!

doubleClickAction
    "action block which is performed on a double button click.
     Number of arguments to the block can be 0 upto 4.

     argument 1: nearest column to the click point
     argument 2: the logical index (X)
     argument 3: the distance y from the click point to the columns's graph
     argument 4: physical y value
    "
    ^ doubleClickBlock
!

doubleClickAction:aBlockUpTo4Args
    "action block which is performed on a double button click.
     Number of arguments to the block can be 0 upto 4.

     argument 1: nearest column to the click point
     argument 2: the logical index (X)
     argument 3: the distance y from the click point to the columns's graph
     argument 4: physical y value
    "
    doubleClickBlock := aBlockUpTo4Args
!

menuAccessBlock
    "action block which is performed if a menu is required; if the block returns
     nil, the default middlebutton menu is evaluated.

     Number of arguments to the block can be 0 upto 4.

     argument 1: nearest column to the click point
     argument 2: the logical index (X)
     argument 3: the distance y from the click point to the columns's graph
     argument 4: physical y value
    "
    ^ menuAccessBlock
!

menuAccessBlock:aBlockUpTo4Args
    "action block which is performed if a menu is required; if the block returns
     nil, the default middlebutton menu is evaluated.

     Number of arguments to the block can be 0 upto 4.

     argument 1: nearest column to the click point
     argument 2: the logical index (X)
     argument 3: the distance y from the click point to the columns's graph
     argument 4: physical y value
    "
    menuAccessBlock:= aBlockUpTo4Args
! !

!GraphColumnView2D methodsFor:'accessing dimensions'!

gridExtent
    "returns the x/y extent of the grid
    "
    ^ gridX @ gridY
!

gridExtent:anExtent
    "set the x/y extent of the grid
    "
    |gX gY|

    anExtent isNil ifTrue:[
	gX := gY := 0
    ] ifFalse:[
	gX := (anExtent x) max:0.
	gY := (anExtent y) max:0.
    ].

    gridX == gX ifTrue:[
	self gridY:gY
    ] ifFalse:[
	gridY := gY.
	self gridX:gX
    ]
!

gridX
    "returns the horizontal size of the grid or 0 if the horizontal grid is disabled
    "
    ^ gridX
!

gridX:aValue
    "set the horizontal size of the grid or 0 if the horizontal grid should be invisible
    "
    |x|

    x := self unsignedIntegerFrom:aValue onError:[gridX].

    x ~~ gridX ifTrue:[
	gridX := x.
	self doRecomputeGraph.
    ]
!

gridY
    "returns the vertical size of the grid or 0 if the vertical grid is disabled
    "
    ^ gridY
!

gridY:aValue
    "set the vertical size of the grid or 0 if the vertical grid should be invisible
    "
    |y|

    y := self unsignedIntegerFrom:aValue onError:[gridY].

    y ~~ gridY ifTrue:[
	gridY := y.
	self doRecomputeGraph
    ]


! !

!GraphColumnView2D methodsFor:'change & update'!

changedGraphIn:aColumn what:what from:oldValue
    "the graph assigned to the column description changed
    "
    |widthC scaleY transY dataY stepX|

    widthC := aColumn lineWidth.
    scaleY := self scaleYofColumn:aColumn.
    transY := self transYofColumn:aColumn.
    stepX  := self stepX.
    dataY  := self yDataForColumn:aColumn.

    what == #lineWidth ifTrue:[
	widthC < oldValue ifTrue:[widthC := oldValue]
			 ifFalse:[widthC := nil]
    ] ifFalse:[
	(what ~~ #lineStyle or:[aColumn lineStyle == #solid]) ifTrue:[
	    widthC := nil
	]
    ].

    widthC notNil ifTrue:[
	self drawGRX:0
		step:stepX
	      scaleY:scaleY
	      transY:transY
	       ydata:dataY
		with:bgColor
	       style:#solid
	       width:widthC.

	what == #lineWidth ifTrue:[
	    ^ self drawX:0 y:0 width:width height:height
	]
    ].

    self drawGRC:aColumn
	       x:0
	    step:stepX
	  scaleY:scaleY
	  transY:transY
	   ydata:dataY.













!

changedHLineIn:aColumn what:what from:oldValue
    "the horizontal lines assigned to the column description changed
    "
    |widthC scaleY transY list|

    widthC := aColumn hLineWidth.
    scaleY := self scaleYofColumn:aColumn.
    transY := self transYofColumn:aColumn.

    what == #hLineList ifTrue:[
	oldValue notNil ifTrue:[list   := oldValue]
		       ifFalse:[widthC := nil].
    ] ifFalse:[
	(list := aColumn hLineList) isNil ifTrue:[
	    ^ self
	].

	what == #hLineWidth ifTrue:[
	    widthC < oldValue ifTrue:[widthC := oldValue]
			     ifFalse:[widthC := nil]
	] ifFalse:[
	    (what ~~ #hLineStyle or:[aColumn hLineStyle == #solid]) ifTrue:[
		widthC := nil
	    ]
	].
    ].

    widthC notNil ifTrue:[
	self drawHLN:list
		   x:0
		   y:0
		 toX:width
		   y:height
	      scaleY:scaleY
	      transY:transY
		with:bgColor
	       style:#solid
	       width:widthC.

	what ~~ #hLineStyle ifTrue:[
	    ^ self drawX:0 y:0 width:width height:height
	]
    ].

    self drawHLC:aColumn
	       x:0
	       y:0
	     toX:width
	       y:height
	  scaleY:scaleY
	  transY:transY.

! !

!GraphColumnView2D methodsFor:'displaying'!

displayOn:aGC
    "ST-80 Compatibility
     display the receiver in a graphicsContext at 0@0
     - this method allows for any object to be displayed in some view
       or on a printer
     (although the fallBack is to display its printString ...)
    "
    self displayOn:aGC x:0 y:0

!

displayOn:aGC at:aPoint
    "ST-80 Compatibility
     display the receiver in a graphicsContext - this method allows
     for any object to be displayed on a Printer - for example.
    "
    self displayOn:aGC x:(aPoint x) y:(aPoint y)

!

displayOn:aGC x:x y:y
    "ST-80 Compatibility
     display the receiver in a graphicsContext - this method allows
     for any object to be displayed on a Printer - for example.
    "
    |w h|

    w := aGC width  - x.
    h := aGC height - y.

    (w > 0 and:[h > 0]) ifTrue:[
	self displayOn:aGC x:x y:y w:w h:(h min:height)
    ] ifFalse:[
	self halt
    ]

!

displayOn:aGC x:x y:y w:w h:h
    |n s sX tY list yData maxX maxY oldClip oldTrans|

    oldClip  := aGC clippingRectangleOrNil.
    oldTrans := aGC transformation.

    sX := (w / (windowSize - 1)) asFloat.
    aGC transformation:(WindowingTransformation scale:1 translation:(x  @ y)).
    aGC clippingRectangle:(Rectangle left:0 top:0 width:w height:h).

    showGrid ifTrue:[
	aGC viewBackground ~= gridColor ifTrue:[
	    aGC paint:gridColor.
	] ifFalse:[
	    aGC paint:(Color green lightened)
	].

	gridX ~~ 0 ifTrue:[
	    s := gridX * self class gridStep.
	    n := 0.
	    [ n < w ] whileTrue:[ aGC displayLineFromX:n y:0 toX:n y:h. n := n + s ]
	].
	gridY > 0 ifTrue:[
	    s := gridY * self class gridStep.
	    n := 0.
	    [ n < h ] whileTrue:[ aGC displayLineFromX:0 y:n toX:w y:n. n := n + s ]
	]
    ].

    (list := self listOfVisibleRefIndices) isEmpty ifFalse:[
	aGC paint:referenceColor.

	list do:[:i||rX|
	    ((rX := (i * sX) rounded) >= 0 and:[rX <= w]) ifTrue:[
		aGC displayLineFromX:rX y:0 toX:rX y:h
	    ]
	]
    ].
    yData := Array new:((w // sX + 2) min:windowSize).
    aGC paint:fgColor.

    self listOfVisibleColumns do:[:aCol|
	tY := zoomY  * aCol zoomY.
	tY := h * (aCol relativeXaxis) / tY + (aCol transY) * tY.

	aGC transformation:(WindowingTransformation scale:(sX @ (self scaleYofColumn:aCol))
					      translation:(x  @ (y + tY))).

	(list := aCol hLineList) size ~~ 0 ifTrue:[
	    list do:[:y|
		aGC paint:(aCol hLineFgColor ? fgColor).
		aGC lineStyle:aCol hLineStyle.
		aGC lineWidth:(aCol hLineWidth).
		aGC displayLineFromX:0 y:y toX:(windowSize - 1) y:y.
	    ]
	].

	(aCol yValuesStartAt:graphOriginX into:yData) keysAndValuesDo:[:i :y|
	    yData at:i put:(i - 1 @ y)
	].
	aGC paint:(aCol foregroundColor ? fgColor).
	aGC lineStyle:aCol lineStyle.
	aGC lineWidth:(aCol lineWidth).
	aGC displayPolygon:yData.
    ].
    aGC transformation:oldTrans.
    aGC clippingRectangle:oldClip.
    aGC lineStyle:#solid.
    aGC lineWidth:0.
    aGC paint:fgColor.
    aGC displayRectangleX:x y:y width:w height:h



!

doPrint
    "print the current visible contents on the printer
    "
    |printer w h|

    (printer := Printer new) isNil ifTrue:[
	self error:'cannot open printer'.
      ^ self
    ].

    self withWaitCursorDo:[
	Printer writeErrorSignal handle:[:ex |
	    self warn:('error while printing:\\'
			, ex errorString
			, '\\(printing with: ' , (Printer printCommand) , ')') withCRs
	] do:[
	    printer setNative:true.
	    printer nextPutAll:'OriginalState setgstate'; cr.
	    printer := PSGraphicsContext on:printer origin:(0 @ 0) corner:( 1.0 @ 1.0 ).
	    w := printer width - printer rightMargin - printer leftMargin.
	    h := printer height min:height.
	    self displayOn:printer x:0 y:0 w:w h:h.
	].
	printer close
    ].



! !

!GraphColumnView2D methodsFor:'drawing'!

clearColumnAndRedraw:aColumn
    "undraw a column and redraw the view without clearing the background
    "
    shown ifTrue:[
	(self sensor hasDamageFor:self) ifTrue:[
	    self invalidate
	] ifFalse:[
	    self undrawColumn:aColumn
		       scaleY:(self scaleYofColumn:aColumn)
		       transY:(self transYofColumn:aColumn)
	]
    ]
!

redrawColumn:aColumn
    "redraw a column including the horizontal lines
     and the graph of the column
    "
    |transY scaleY stepX values|

    (shown and:[aColumn shown]) ifTrue:[
	scaleY := self scaleYofColumn:aColumn.
	transY := self transYofColumn:aColumn.
	stepX  := self stepX.

	self drawHLC:aColumn
		   x:0
		   y:0
		 toX:width
		   y:height
	      scaleY:scaleY
	      transY:transY.

	self drawGRC:aColumn
		   x:0
		step:stepX
	      scaleY:scaleY
	      transY:transY
	       ydata:(self yDataForColumn:aColumn)
    ]


!

redrawColumnAt:anIndex
    "redraw a column at an index including the horizontal lines
     and the graph of the column
    "
    self redrawColumn:(columns at:anIndex)
!

redrawX:x y:y width:w height:h
    "clear and redraw
    "
    shown ifTrue:[
	self paint:bgColor.
	self fillRectangleX:x y:y width:w height:h.
	self drawX:x y:y width:w height:h.
    ].
! !

!GraphColumnView2D methodsFor:'drawing basics'!

drawGRC:aColumn x:xStart step:xStep scaleY:yScale transY:yTrans ydata:yData
    "draw a graph
    "
    self drawGRX:xStart
	    step:xStep
	  scaleY:yScale
	  transY:yTrans
	   ydata:yData
	    with:(self mapColor:(aColumn foregroundColor))
	   style:(aColumn lineStyle)
	   width:(aColumn lineWidth)
!

drawGRX:xStart step:xStep scaleY:yScale transY:yTrans ydata:ydata with:aColor style:aStyle width:aWidth
    "draw a graph
    "
"
         self test1
"
    |xNext xLast yLast yNext|

    ydata size == 0 ifTrue:[^ self].

    self paint:aColor.
    self lineStyle:aStyle.
    self lineWidth:aWidth.

    ydata size == 0 ifTrue:[^ self].

    xLast := xStart.
    yLast := (ydata at:1) * yScale + yTrans.

    ydata from:2 do:[:y |
        xNext := xLast + xStep.
        yNext := y * yScale + yTrans.
        self displayLineFromX:xLast rounded y:yLast rounded toX:xNext rounded y:yNext rounded.
        xLast := xNext.
        yLast := yNext.
    ].
!

drawHLC:aColumn x:x y:y toX:xMax y:yMax scaleY:yScale transY:yTrans
    "draw horizontal lines derrived from column
    "
    |list|

    (list := aColumn hLineList) notNil ifTrue:[
	self drawHLN:list
		   x:x
		   y:y
		 toX:xMax
		   y:yMax
	      scaleY:yScale
	      transY:yTrans
		with:(self mapColor:(aColumn hLineFgColor))
	       style:(aColumn hLineStyle)
	       width:(aColumn hLineWidth)
    ].


!

drawHLN:aList x:x y:y toX:xMax y:yMax scaleY:yScale transY:yTrans with:aColor style:aStyle width:aWidth
    "draw horizontal lines derrived from list
    "
    self paint:aColor.
    self lineStyle:aStyle.
    self lineWidth:aWidth.

    aList do:[:hY||dY|
	dY := (hY * yScale + yTrans) rounded.

	(dY < y or:[dY > yMax]) ifFalse:[
	    self displayLineFromX:x y:dY toX:xMax y:dY.
	]
    ].
    aStyle ~~ #solid ifTrue:[self lineStyle:#solid].
    aWidth ~~ 1      ifTrue:[self lineWidth:1].


!

drawReferencesFromX:x0 y:y0 to:x1 y:y1
    "redraw visible references
    "
    |x stepX refLines|

    refLines := self listOfVisibleRefIndices.

    refLines notEmpty ifTrue:[
	stepX := self stepX.

	self paint:referenceColor.

	refLines do:[:anIndex|
	    x := (anIndex * stepX) rounded.

	    (x >= x0 and:[x <= x1]) ifTrue:[
		self displayLineFromX:x y:y0 toX:x y:y1
	    ]
	]
    ].
!

drawX:x y:y width:w height:h
    "redraw without clearing the background
    "
    |saveClip yValues xStep xStart yScale yTrans
     xIndex "{ Class:SmallInteger }"
     x0     "{ Class:SmallInteger }"
     y0     "{ Class:SmallInteger }"
     xMax   "{ Class:SmallInteger }"
     yMax   "{ Class:SmallInteger }"
     step   "{ Class:SmallInteger }"
     gstep  "{ Class:SmallInteger }"
     start  "{ Class:SmallInteger }"
     stop   "{ Class:SmallInteger }"
    |
    xStep := self stepX.
    xMax  := x + w.
    yMax  := y + h.
    x0    := x // xStep.
    start := x0 + 1.
    stop  := (xMax // xStep + 2) min:windowSize.

    start < stop ifFalse:[^ self].

    saveClip := clipRect.
    self clippingRectangle:(Rectangle left:x top:y width:w height:h).

    showGrid ifTrue:[
	self paint:gridColor.
	gstep := self class gridStep.

"/ X-Grid
	gridX ~~ 0 ifTrue:[
	    step := gridX * gstep.
	    x0 := (x // step) * step + gridXoffset.

	    [ x0 < xMax ] whileTrue:[
		self displayLineFromX:x0 y:y toX:x0 y:yMax.
		x0 := x0 + step.
	    ]
	].

"/ Y-Grid
	gridY > 0 ifTrue:[
	    step := gridY * gstep.

	    y0 := (y // step) * step.

	    [ y0 < yMax ] whileTrue:[
		self displayLineFromX:x y:y0 toX:xMax y:y0.
		y0 := y0 + step
	    ]
	]
    ].

    columns notNil ifTrue:[
	xStart  := start - 1 * xStep.
	xIndex  := start - 1 + graphOriginX.
	yValues := Array new:(stop - start + 1).

"/ Column
	columns do:[:aCol|
	    aCol shown ifTrue:[
		yScale := self scaleYofColumn:aCol.
		yTrans := self transYofColumn:aCol.

		self drawHLC:aCol x:x y:y toX:xMax y:yMax scaleY:yScale transY:yTrans.

		self drawGRC:aCol x:xStart step:xStep scaleY:yScale transY:yTrans
		       ydata:(aCol yValuesStartAt:xIndex into:yValues)
	    ]
	].

"/ V-Lines
	self drawReferencesFromX:x y:y to:xMax y:yMax.
    ].

    self clippingRectangle:saveClip.
!

undrawColumn:aColumn scaleY:scaleY transY:transY
    "undraw a column, than redraw all without clearing the background
    "
    |stepX hlines|

    stepX := self stepX.

    (hlines := aColumn hLineList) notNil ifTrue:[
	self drawHLN:hlines
		   x:0
		   y:0
		 toX:width
		   y:height
	      scaleY:scaleY
	      transY:transY
		with:bgColor
	       style:#solid
	       width:(aColumn hLineWidth)
    ].

    self drawGRX:0
	    step:stepX
	  scaleY:scaleY
	  transY:transY
	   ydata:(self yDataForColumn:aColumn)
	    with:bgColor
	   style:#solid
	   width:(aColumn lineWidth).

    self drawX:0 y:0 width:width height:height
! !

!GraphColumnView2D methodsFor:'event handling'!

buttonMultiPress:button x:x y:y
    "handle a button double click event
    "
    ((button == 1) or:[button == #select]) ifFalse:[
	^ super buttonMultiPress:button x:x y:y
    ].
    self buttonPressBlock:doubleClickBlock x:x y:y
!

buttonPress:button x:x y:y
    "handle a button press event
    "
    |menu|

    ((button == 2) or:[button == #menu]) ifTrue:[
	menu := self buttonPressBlock:menuAccessBlock x:x y:y.

	menu notNil ifTrue:[
	    ^ menu startUp
	]
    ] ifFalse:[
	((button == 1) or:[button == #select]) ifTrue:[
	    ^ self buttonPressBlock:actionBlock x:x y:y
	]
    ].

    super buttonPress:button x:x y:y
!

buttonPressBlock:aBlock x:x y:y
    "evaluate the user defined block if not nil dependent on its required
     arguments; the result of the block is returned
    "
    |numArgs desc index|

    aBlock isNil ifTrue:[
	^ nil
    ].
    (numArgs := aBlock numArgs) == 0 ifTrue:[
	^ aBlock value
    ].

    (desc := self nearestColumnAtX:x y:y) isNil ifTrue:[
	^ nil
    ].
    index := self absoluteIndexOfX:x.

    numArgs == 1 ifTrue:[ ^ aBlock value:(desc key) ].
    numArgs == 2 ifTrue:[ ^ aBlock value:(desc key) value:index ].
    numArgs == 3 ifTrue:[ ^ aBlock value:(desc key) value:index value:(desc value) ].

    ^ aBlock value:(desc key) value:index value:(desc value) value:y

!

buttonRelease:button x:x y:y
    "handle a button release event
    "
    |numArgs|

    buttonReleaseBlock isNil ifTrue:[
	^ super buttonRelease:button x:x y:y
    ].
    numArgs := buttonReleaseBlock numArgs.

    numArgs == 0 ifTrue:[ ^ buttonReleaseBlock value ].
    numArgs == 1 ifTrue:[ ^ buttonReleaseBlock value:x ].
    numArgs == 2 ifTrue:[ ^ buttonReleaseBlock value:x value:y ].

    buttonReleaseBlock value:x value:y value:(self absoluteIndexOfX:x)
! !

!GraphColumnView2D methodsFor:'initialize'!

initialize
    "setup default values
    "
    super initialize.

    gridXoffset  := 0.
    colorMap     := Dictionary new.
    gridX        := 2.
    gridY        := 2.
!

unrealize
    "clear colorMap
    "
    super unrealize.
    colorMap := Dictionary new.

! !

!GraphColumnView2D methodsFor:'private'!

mapColor:aColor
    "get the same color on the device. If the argument is
     nil, the foreground color is returned.
    "
    |fg|

    aColor isNil ifTrue:[
        ^ fgColor
    ].
    (fg := colorMap at:aColor ifAbsent:nil) isNil ifTrue:[
        colorMap at:aColor put:(fg := aColor onDevice:device)
    ].
    ^ fg
!

yDataForColumn:aColumn
    "returns collection of visible Y-data for a column
    "
    ^ aColumn yValuesStartAt:graphOriginX into:(Array new:windowSize)


! !

!GraphColumnView2D methodsFor:'protocol'!

doRecomputeGraph
    "called to recompute drawable objects and to set the graph to invalidate
    "
    gridXoffset := 0.

    shown ifTrue:[
	self invalidate.
    ]

!

updateColumns:what with:oldValue from:aColumn
    "called if the list of columns changed
	 #size      the size of the columns
	 #color:    color changed
     or a specific column:( aColumn notNil )
	 #insert:   insert a new column
	 #remove:   remove a column

	or a specific attribute derived from the
	changed column.
    "
    |colSY colZY colTY colRX|

    shown ifFalse:[
	^ self
    ].

    (what == nil or:[self sensor hasDamageFor:self]) ifTrue:[
	^ self invalidate
    ].

    aColumn isNil ifTrue:[
	^ self doRecomputeGraph
    ].

    what == #insert: ifTrue:[ ^ self redrawColumn:aColumn ].
    what == #remove: ifTrue:[ ^ self clearColumnAndRedraw:aColumn ].

    what == #shown ifTrue:[
	aColumn shown ifTrue:[self redrawColumn:aColumn]
		     ifFalse:[self clearColumnAndRedraw:aColumn].

	^ self
    ].

    aColumn shown ifFalse:[ ^ self ].

    (    what == #lineStyle
     or:[what == #foregroundColor
     or:[what == #lineWidth]]
    ) ifTrue:[
	^ self changedGraphIn:aColumn what:what from:oldValue
    ].

    (    what == #hLineStyle
     or:[what == #hLineFgColor
     or:[what == #hLineWidth
     or:[what == #hLineList ]]]
    ) ifTrue:[
	^ self changedHLineIn:aColumn what:what from:oldValue
    ].
    colSY := aColumn scaleY.
    colZY := aColumn zoomY.
    colTY := aColumn transY.
    colRX := aColumn relativeXaxis.

    what == #scaleY        ifTrue:[ colSY := oldValue ] ifFalse:[
    what == #transY        ifTrue:[ colTY := oldValue ] ifFalse:[
    what == #relativeXaxis ifTrue:[ colRX := oldValue ] ifFalse:[
    what == #zoomY         ifTrue:[ colZY := oldValue ] ifFalse:[ ^ self doRecomputeGraph ]]]].

    self undrawColumn:aColumn
	       scaleY:(self absScaleY:colSY zoomY:colZY)
	       transY:(self absTransY:colTY
			   relativeTo:colRX
				zoomY:colZY
		     ).



!

updateOriginX:nIndices
    "scroll left or right n x-steps. A positive value scrolls to the right
     a negative value to the left.
    "
    |
     x "{ Class:SmallInteger }"
     w "{ Class:SmallInteger }"
     gridDeltaX "{ Class:SmallInteger }"
    |

    x := (nIndices * self stepX) rounded.
    w := width - (x abs).

 "/ update offset X for the grid

    (showGrid and:[gridX ~~ 0]) ifTrue:[
	gridDeltaX  := gridXoffset + x.
	gridXoffset := gridDeltaX \\ (gridX * self class gridStep).

	gridDeltaX < 0 ifTrue:[
	    gridXoffset := gridXoffset negated
	].
    ].

 "/ scrolling & redraw

    x := x abs.

    self catchExpose.

    nIndices < 0 ifTrue:[       "/ scroll left

	self copyFrom:self x:x  y:0 toX:0 y:0 width:w height:height async:true.
	self redrawX:w y:0 width:x height:height.

    ] ifFalse:[                 "/ scroll right

	self copyFrom:self x:0  y:0 toX:x y:0 width:w height:height async:true.
	self redrawX:0 y:0 width:x height:height.
    ].
    self waitForExpose.
!

updateReferences:what atRelX:relX
    "called when the list of references changed.
	#remove:        the reference at the relative X index is removed
	#insert:        a reference is inserted at the relative X index
	#size           the list of references changed
	#state          visibility state changed
	#color          the foreground color changed
    "
    |x|

    shown ifTrue:[
	showReferences ifFalse:[
	    what == #state ifTrue:[
		self doRecomputeGraph
	    ]
	] ifTrue:[
	    (what == #color or:[what == #state]) ifTrue:[
		self drawReferencesFromX:0 y:0 to:width y:height
	    ] ifFalse:[
		(what == #insert: or:[what == #remove:]) ifTrue:[
		    x := (relX * self stepX) rounded.

		    what == #insert: ifTrue:[
			self paint:referenceColor.
			self displayLineFromX:x y:0 toX:x y:height
		    ] ifFalse:[
			self redrawX:x y:0 width:1 height:height
		    ]
		] ifFalse:[
		    self doRecomputeGraph
		]
	    ]
	]
    ]
! !

!GraphColumnView2D methodsFor:'queries'!

absoluteIndexOfX:x
    "returns the absolute X value for a visible x
    "
    ^ (x // self stepX) + graphOriginX

!

nearestColumnAtX:x y:y
    "returns an association containing the nearest column as key and the absolute y-distance
     to the point x/y. If no columns exists nil is returned
    "
    |sX dX dtY data col
     i0   "{ Class:SmallInteger }"
    |

    columns size == 0 ifTrue:[^ nil ].

    col := nil.
    sX  := self stepX.
    i0  := x // sX.

    sX > 1 ifFalse:[
	dX := 0
    ] ifTrue:[
	(dX := x - (i0 * sX)) > 0.5 ifTrue:[dX := dX / sX]
				   ifFalse:[dX := 0]
    ].
    i0 := (i0 + 1) min:windowSize.

    (i0 < windowSize and:[dX ~= 0]) ifTrue:[data := Array new:2]
				   ifFalse:[data := Array new:1].

    i0 := i0 + graphOriginX.

    columns do:[:aCol||vlY scY trY yL yR|
	aCol shown ifTrue:[
	    vlY := aCol yValuesStartAt:i0 into:data.
	    scY := self scaleYofColumn:aCol.
	    trY := self transYofColumn:aCol.
	    yL  := vlY first * scY.
	    yR  := vlY last  * scY.
	    yL  := (yL + trY + (yR - yL * dX)) rounded.
	    yR  := (y - yL) abs.

	    (col isNil or:[dtY > yR]) ifTrue:[
		dtY := yR.
		col := aCol.
	    ]
	]
    ].

    ^ col notNil ifTrue:[Association key:col value:dtY] ifFalse:[nil]

! !

!GraphColumnView2D methodsFor:'transformations'!

absScaleY:aNumber zoomY:aZoomY
    "returns y-scale for a scale Y and a zoom factor
    "
    ^ (aNumber negated) * zoomY * aZoomY
!

absTransY:aNumber relativeTo:xAxis zoomY:aZoomY
    "returns absolute transition Y for a number relative
     to a x-axis
    "
    |zY|

    zY := zoomY  * aZoomY.
  ^ height * xAxis / zY + aNumber * zY



!

scaleYofColumn:aColumn
    "returns current y-scale of a column on my view
    "
    ^ (aColumn scaleY negated) * zoomY * (aColumn zoomY)
!

stepX
    "returns width of X measured in pixels; no fraction returned
    "
    |x|

    x := width / (windowSize - 1).
  ^ x isInteger ifTrue:[x] ifFalse:[x asFloat]
!

transYofColumn:aColumn
    "returns current y-transition of a column on my view
    "
    ^ self absTransY:(aColumn transY)
	  relativeTo:(aColumn relativeXaxis)
	       zoomY:(aColumn zoomY)
! !

!GraphColumnView2D methodsFor:'user interaction & notifications'!

doGridExtent:what
    "change the grid extent factorial 2 (larger or smaller)
    "
    |ext|

    ext := self gridExtent.

    what == #larger ifTrue:[
	ext := ext * 2
    ] ifFalse:[
	what == #smaller ifFalse:[
	^ self
	].
	ext := ext // 2.
	(ext x == 0) ifTrue:[gridX ~~ 0 ifTrue:[ext x:1]].
	(ext y == 0) ifTrue:[gridY ~~ 0 ifTrue:[ext y:1]].
    ].
    self gridExtent:ext.

!

doGridStepX:aNumberOrSymbol
    "change the grid step X
    "
    self gridX:(aNumberOrSymbol == #off ifFalse:[aNumberOrSymbol] ifTrue:[0])
!

doGridStepY:aNumberOrSymbol
    "change the grid step Y
    "
    self gridY:(aNumberOrSymbol == #off ifFalse:[aNumberOrSymbol] ifTrue:[0])
! !

!GraphColumnView2D class methodsFor:'documentation'!

version
    ^ '$Header$'
! !