module Webglimpse { export interface TimelineTimeseriesPainterOptions { timelineFont : string ; timelineFgColor : Color ; timelineThickness : number ; rowTopPadding : number ; rowBottomPadding : number ; } export interface TimelineTimeseriesPainterFactory { ( drawable , timeAxis , dataAxis , model , rowModel , ui , options ) : Painter ; } export interface TimelineTimeseriesRowPaneOptions { rowHeight ? : number ; rowTopPadding ? : number ; rowBottomPadding ? : number ; axisOptions ? : EdgeAxisPainterOptions ; axisWidth ? : number ; painterFactories ? : TimelineTimeseriesPainterFactory [ ] ; } export function newTimeseriesRowPaneFactory ( rowOptions ? ) { return function ( drawable , timeAxis , dataAxis , model , row , ui , options ) : Pane { var rowTopPadding = ( hasval ( rowOptions ) && hasval ( rowOptions . rowTopPadding ) ? rowOptions . rowTopPadding : 6 ) ; var rowBottomPadding = ( hasval ( rowOptions ) && hasval ( rowOptions . rowBottomPadding ) ? rowOptions . rowBottomPadding : 6 ) ; var axisWidth = ( hasval ( rowOptions ) && hasval ( rowOptions . axisWidth ) ? rowOptions . axisWidth : 60 ) ; var painterFactories = ( hasval ( rowOptions ) && hasval ( rowOptions . painterFactories ) ? rowOptions . painterFactories : [ ] ) ; var axisOptions = ( hasval ( rowOptions ) && hasval ( rowOptions . axisOptions ) ? rowOptions . axisOptions : { } ) ; var keyPrefix = options . isMaximized ? 'maximized-' : '' ; var getRowHeight = function ( ) { if ( options . isMaximized ) { return null ; } else if ( hasval ( row . rowHeight ) ) { return row . rowHeight ; } else if ( hasval ( rowOptions ) && hasval ( rowOptions . rowHeight ) ) { return rowOptions . rowHeight ; } else { return 135 ; } } ; var rowHeight = getRowHeight ( ) ; var timelineFont = options . timelineFont ; var timelineFgColor = options . timelineFgColor ; var draggableEdgeWidth = options . draggableEdgeWidth ; var snapToDistance = options . snapToDistance ; var rowUi = ui . rowUi ( row . rowGuid ) ; var input = ui . input ; var selection = ui . selection ; if ( ! hasval ( axisOptions . font ) ) axisOptions . font = timelineFont ; if ( ! hasval ( axisOptions . tickColor ) ) axisOptions . tickColor = timelineFgColor ; if ( ! hasval ( axisOptions . textColor ) ) axisOptions . textColor = timelineFgColor ; if ( ! hasval ( axisOptions . showLabel ) ) axisOptions . showLabel = true ; if ( ! hasval ( axisOptions . shortenLabels ) ) axisOptions . shortenLabels = false ; var redraw = function ( ) { drawable . redraw ( ) ; } ; var yAxisPane = new Pane ( { updatePrefSize : fixedSize ( axisWidth , rowHeight ) } ) ; dataAxis . limitsChanged . on ( redraw ) ; attachAxisMouseListeners1D ( yAxisPane , dataAxis , true ) ; var updateRowHeight = function ( ) { yAxisPane . layout = { updatePrefSize : fixedSize ( axisWidth , getRowHeight ( ) ) } ; } ; row . attrsChanged . on ( updateRowHeight ) ; var isDragMode = function ( viewport , i , j ) : boolean { var fragment = getNearestFragment ( viewport , i , j ) . fragment ; return hasval ( fragment ) ; } ; var rowContentPane = new Pane ( newColumnLayout ( ) , true , isDragMode ) ; var underlayPane = new Pane ( newOverlayLayout ( ) , false ) ; var overlayPane = new Pane ( null , false ) ; var painterOptions = { timelineFont : timelineFont , timelineFgColor : timelineFgColor , timelineThickness : 1 , rowTopPadding : rowTopPadding , rowBottomPadding : rowBottomPadding } ; for ( var n = 0 ; n < painterFactories . length ; n ++ ) { var createPainter = painterFactories [ n ] ; rowContentPane . addPainter ( createPainter ( drawable , timeAxis , dataAxis , model , row , ui , painterOptions ) ) ; } yAxisPane . addPainter ( newEdgeAxisPainter ( dataAxis , Side . RIGHT , axisOptions ) ) ; rowContentPane . addPane ( yAxisPane , 0 ) ; underlayPane . addPane ( rowContentPane , true ) ; underlayPane . addPane ( overlayPane , false ) ; rowUi . addPane ( keyPrefix + 'content' , rowContentPane ) ; rowUi . addPane ( keyPrefix + 'overlay' , overlayPane ) ; rowUi . addPane ( keyPrefix + 'underlay' , underlayPane ) ; rowUi . addPane ( keyPrefix + 'y-axis' , yAxisPane ) ; row . timeseriesGuids . valueAdded . on ( redraw ) ; row . timeseriesGuids . valueMoved . on ( redraw ) ; row . timeseriesGuids . valueRemoved . on ( redraw ) ; var addFragmentRedraw = function ( fragmentGuid ) { var fragment = model . timeseriesFragment ( fragmentGuid ) ; fragment . dataChanged . on ( redraw ) ; } ; var removeFragmentRedraw = function ( fragmentGuid ) { var fragment = model . timeseriesFragment ( fragmentGuid ) ; fragment . dataChanged . off ( redraw ) ; } ; var addRedraw = function ( timeseriesGuid ) { var timeseries = model . timeseries ( timeseriesGuid ) ; timeseries . attrsChanged . on ( redraw ) ; timeseries . fragmentGuids . valueAdded . on ( redraw ) ; timeseries . fragmentGuids . valueRemoved . on ( redraw ) ; timeseries . fragmentGuids . forEach ( addFragmentRedraw ) ; timeseries . fragmentGuids . valueAdded . on ( addFragmentRedraw ) ; timeseries . fragmentGuids . valueRemoved . on ( removeFragmentRedraw ) ; } ; row . timeseriesGuids . forEach ( addRedraw ) ; row . timeseriesGuids . valueAdded . on ( addRedraw ) ; var removeRedraw = function ( timeseriesGuid ) { var timeseries = model . timeseries ( timeseriesGuid ) ; timeseries . attrsChanged . off ( redraw ) ; timeseries . fragmentGuids . valueAdded . off ( redraw ) ; timeseries . fragmentGuids . valueRemoved . off ( redraw ) ; timeseries . fragmentGuids . forEach ( removeFragmentRedraw ) ; } ; row . timeseriesGuids . valueRemoved . on ( removeRedraw ) ; var timeAtCoords_PMILLIS = function ( viewport , i ) : number { return timeAxis . tAtFrac_PMILLIS ( viewport . xFrac ( i ) ) ; } ; var timeAtPointer_PMILLIS = function ( ev ) : number { return timeAtCoords_PMILLIS ( ev . paneViewport , ev . i ) ; } ; var timeseriesDragMode = null ; var recentMouseMove = null ; rowContentPane . mouseMove . on ( function ( ev ) { input . mouseMove . fire ( ev ) ; if ( ! hasval ( timeseriesDragMode ) ) { input . timeHover . fire ( timeAtPointer_PMILLIS ( ev ) , ev ) ; input . rowHover . fire ( row , ev ) ; } recentMouseMove = ev ; } ) ; rowContentPane . mouseExit . on ( function ( ev ) { input . mouseExit . fire ( ev ) ; if ( ! hasval ( timeseriesDragMode ) ) { input . timeHover . fire ( null , ev ) ; input . rowHover . fire ( null , ev ) ; input . eventHover . fire ( null , ev ) ; } recentMouseMove = null ; } ) ; var uiMillisPerPxChanged = function ( ) { if ( ! hasval ( timeseriesDragMode ) && recentMouseMove != null ) { var ev = recentMouseMove ; input . timeHover . fire ( timeAtPointer_PMILLIS ( ev ) , ev ) ; } } ; ui . millisPerPx . changed . on ( uiMillisPerPxChanged ) ; rowContentPane . mouseUp . on ( function ( ev ) { input . mouseUp . fire ( ev ) ; } ) ; rowContentPane . mouseDown . on ( function ( ev ) { input . mouseDown . fire ( ev ) ; } ) ; rowContentPane . mouseWheel . on ( options . mouseWheelListener ) ; rowContentPane . contextMenu . on ( function ( ev ) { input . contextMenu . fire ( ev ) ; } ) ; var getNearestAnnotation = function ( viewport , i , j ) { var pickBuffer_PIXEL = 10 ; var vppx = ui . millisPerPx . value ; var vppy = dataAxis . vSize / rowContentPane . viewport . h ; var pickBuffer_PMILLIS = pickBuffer_PIXEL * vppx ; var ev_time = timeAtCoords_PMILLIS ( viewport , i ) ; var ev_value = dataAxis . vAtFrac ( viewport . yFrac ( j ) ) ; var bestAnnotation = null ; var best_PIXEL = null ; if ( ev_time ) { for ( var i = 0 ; i < row . annotationGuids . length ; i ++ ) { var annotationGuid = row . annotationGuids . valueAt ( i ) ; var annotation = model . annotation ( annotationGuid ) ; var styleGuid = annotation . styleGuid ; var style = ui . annotationStyle ( styleGuid ) ; var dy_PIXEL = Math . abs ( annotation . y - ev_value ) / vppy ; var dx_PIXEL = Math . abs ( annotation . time_PMILLIS - ev_time ) / vppx ; if ( style . uiHint == 'point' ) { var d_PIXEL = Math . sqrt ( dx_PIXEL * dx_PIXEL + dy_PIXEL * dy_PIXEL ) ; } else if ( style . uiHint == 'horizontal-line' ) { var d_PIXEL = dy_PIXEL ; } else if ( style . uiHint == 'vertical-line' ) { var d_PIXEL = dx_PIXEL ; } if ( d_PIXEL < pickBuffer_PIXEL ) { if ( ! hasval ( best_PIXEL ) || d_PIXEL < best_PIXEL ) { bestAnnotation = annotation ; best_PIXEL = d_PIXEL ; } } } } return bestAnnotation ; } ; var getNearestAnnotationEvent = function ( ev ) { return getNearestAnnotation ( ev . paneViewport , ev . i , ev . j ) ; } ; overlayPane . mouseMove . on ( function ( ev ) { var y = dataAxis . vAtFrac ( yFrac ( ev ) ) ; selection . hoveredY . value = y ; var result = getNearestAnnotationEvent ( ev ) ; selection . hoveredAnnotation . value = result ; } ) ; selection . hoveredAnnotation . changed . on ( redraw ) ; overlayPane . mouseExit . on ( function ( ) { selection . hoveredY . value = undefined ; selection . hoveredAnnotation . value = null ; } ) ; function chooseTimeseriesDragMode ( ui , hoveredTimeseriesFragment ) { if ( ! hasval ( hoveredTimeseriesFragment ) ) { return null ; } else { return hoveredTimeseriesFragment . userEditMode ; } } var updateCursor = function ( ) { if ( ! timeseriesDragMode ) { var mouseCursors = { 'xy' : 'move' , 'y' : 'ns-resize' } ; rowContentPane . mouseCursor = mouseCursors [ chooseTimeseriesDragMode ( ui , selection . hoveredTimeseries . fragment ) ] ; } } ; ui . millisPerPx . changed . on ( updateCursor ) ; selection . hoveredTimeseries . changed . on ( updateCursor ) ; var getNearestFragment = function ( viewport , i , j ) { var pickBuffer_PIXEL = 10 ; var vppx = ui . millisPerPx . value ; var vppy = dataAxis . vSize / rowContentPane . viewport . h ; var pickBuffer_PMILLIS = pickBuffer_PIXEL * vppx ; var bestFragment ; var bestIndex ; var best_PIXEL ; var ev_time = timeAtCoords_PMILLIS ( viewport , i ) ; var ev_value = dataAxis . vAtFrac ( viewport . yFrac ( j ) ) ; if ( ev_time ) { for ( var i = 0 ; i < row . timeseriesGuids . length ; i ++ ) { var timeseriesGuid = row . timeseriesGuids . valueAt ( i ) ; var timeseries = model . timeseries ( timeseriesGuid ) ; for ( var j = 0 ; j < timeseries . fragmentGuids . length ; j ++ ) { var fragmentGuid = timeseries . fragmentGuids . valueAt ( j ) ; var fragment = model . timeseriesFragment ( fragmentGuid ) ; if ( fragment . start_PMILLIS - pickBuffer_PMILLIS < ev_time && fragment . end_PMILLIS + pickBuffer_PMILLIS > ev_time ) { var index = timeseries . uiHint == 'bars' ? indexAtOrBefore ( fragment . times_PMILLIS , ev_time ) : indexNearest ( fragment . times_PMILLIS , ev_time ) ; var value = fragment . data [ index ] ; var time = fragment . times_PMILLIS [ index ] ; var dy_PIXEL = ( value - ev_value ) / vppy ; var dx_PIXEL = ( time - ev_time ) / vppx ; var d_PIXEL = Math . sqrt ( dx_PIXEL * dx_PIXEL + dy_PIXEL * dy_PIXEL ) ; var filter = function ( ) : boolean { if ( timeseries . uiHint == 'bars' ) { return ( timeseries . baseline < ev_value && ev_value < value ) || ( timeseries . baseline > ev_value && ev_value > value ) ; } else { return d_PIXEL < pickBuffer_PIXEL ; } } ; if ( ( ! best_PIXEL || d_PIXEL < best_PIXEL ) && filter ( ) ) { best_PIXEL = d_PIXEL ; bestFragment = fragment ; bestIndex = index ; } } } } } return { fragment : bestFragment , index : bestIndex } ; } ; var getNearestFragmentEvent = function ( ev ) { return getNearestFragment ( ev . paneViewport , ev . i , ev . j ) ; } ; rowContentPane . mouseMove . on ( function ( ev ) { if ( ! hasval ( timeseriesDragMode ) ) { var result = getNearestFragmentEvent ( ev ) ; selection . hoveredTimeseries . setValue ( result . fragment , result . index ) ; } } ) ; selection . hoveredTimeseries . changed . on ( redraw ) ; rowContentPane . mouseExit . on ( function ( ) { selection . hoveredTimeseries . clearValue ( ) ; } ) ; rowContentPane . mouseDown . on ( function ( ev ) { if ( isLeftMouseDown ( ev . mouseEvent ) ) { timeseriesDragMode = chooseTimeseriesDragMode ( ui , selection . hoveredTimeseries . fragment ) ; } } ) ; rowContentPane . mouseMove . on ( function ( ev ) { if ( hasval ( timeseriesDragMode ) ) { var x = timeAtPointer_PMILLIS ( ev ) ; var y = dataAxis . vAtFrac ( yFrac ( ev ) ) ; var fragment = selection . hoveredTimeseries . fragment ; var fragment_time = fragment . times_PMILLIS ; if ( timeseriesDragMode === 'y' ) { fragment . setData ( selection . hoveredTimeseries . index , y ) ; } else if ( timeseriesDragMode === 'xy' ) { var index = fragment . setData ( selection . hoveredTimeseries . index , y , x ) ; if ( index !== selection . hoveredTimeseries . index ) { selection . hoveredTimeseries . setValue ( fragment , index ) ; } } } } ) ; rowContentPane . mouseUp . on ( function ( ev ) { timeseriesDragMode = null ; } ) ; rowContentPane . dispose . on ( function ( ) { rowUi . removePane ( keyPrefix + 'content' ) ; rowUi . removePane ( keyPrefix + 'overlay' ) ; rowUi . removePane ( keyPrefix + 'underlay' ) ; rowUi . removePane ( keyPrefix + 'y-axis' ) ; dataAxis . limitsChanged . off ( redraw ) ; row . timeseriesGuids . valueAdded . off ( redraw ) ; row . timeseriesGuids . valueMoved . off ( redraw ) ; row . timeseriesGuids . valueRemoved . off ( redraw ) ; row . timeseriesGuids . valueAdded . off ( addRedraw ) ; row . timeseriesGuids . valueRemoved . off ( removeRedraw ) ; selection . hoveredTimeseries . changed . off ( redraw ) ; row . attrsChanged . off ( updateRowHeight ) ; row . timeseriesGuids . forEach ( function ( timeseriesGuid ) { var timeseries = model . timeseries ( timeseriesGuid ) ; timeseries . attrsChanged . off ( redraw ) ; timeseries . fragmentGuids . valueAdded . off ( redraw ) ; timeseries . fragmentGuids . valueRemoved . off ( redraw ) ; } ) ; } ) ; return underlayPane ; } ; } export function newTimeseriesPainterFactory ( options ? ) { return function ( drawable , timeAxis , dataAxis , model , rowModel , ui ) : Painter { var selection = ui . selection ; var defaultColor = hasval ( options ) && hasval ( options . timelineFgColor ) ? options . timelineFgColor : white ; var defaultThickness = hasval ( options ) && hasval ( options . timelineThickness ) ? options . timelineThickness : 1 ; var modelview_pointsize_VERTSHADER = concatLines ( '    uniform mat4 u_modelViewMatrix;                       ' , '    attribute vec4 a_Position;                            ' , '    uniform float u_PointSize;                            ' , '                                                          ' , '    void main( ) {                                        ' , '        gl_PointSize = u_PointSize ;                      ' , '        gl_Position = u_modelViewMatrix * a_Position ;    ' , '    }                                                     ' , '                                                          ' ) ; var program = new Program ( modelview_pointsize_VERTSHADER , solid_FRAGSHADER ) ; var u_Color = new UniformColor ( program , 'u_Color' ) ; var u_modelViewMatrix = new UniformMatrix4f ( program , 'u_modelViewMatrix' ) ; var a_Position = new Attribute ( program , 'a_Position' ) ; var u_PointSize = new Uniform1f ( program , 'u_PointSize' ) ; var axis = new Axis2D ( timeAxis , dataAxis ) ; var xys = new Float32Array ( 0 ) ; var xysBuffer = newDynamicBuffer ( ) ; return function ( gl , viewport ) { gl . blendFuncSeparate ( GL . SRC_ALPHA , GL . ONE_MINUS_SRC_ALPHA , GL . ONE , GL . ONE_MINUS_SRC_ALPHA ) ; gl . enable ( GL . BLEND ) ; program . use ( gl ) ; u_modelViewMatrix . setData ( gl , glOrthoAxis ( axis ) ) ; for ( var i = 0 ; i < rowModel . timeseriesGuids . length ; i ++ ) { var totalSize = 0 ; var sortedFragments = new Array < TimelineTimeseriesFragmentModel > ( ) ; var timeseriesGuid = rowModel . timeseriesGuids . valueAt ( i ) ; var timeseries = model . timeseries ( timeseriesGuid ) ; for ( var j = 0 ; j < timeseries . fragmentGuids . length ; j ++ ) { var timeseriesFragmentGuid = timeseries . fragmentGuids . valueAt ( j ) ; var fragment = model . timeseriesFragment ( timeseriesFragmentGuid ) ; sortedFragments . push ( fragment ) ; totalSize += fragment . times_PMILLIS . length ; } sortedFragments . sort ( ( a , b ) => { return a . start_PMILLIS - b . start_PMILLIS ; } ) ; if ( timeseries . uiHint == 'lines' || timeseries . uiHint == 'points' || timeseries . uiHint == 'lines-and-points' || timeseries . uiHint == undefined ) { var size = totalSize * 2 ; xys = ensureCapacityFloat32 ( xys , size ) ; var index = 0 ; for ( var j = 0 ; j < sortedFragments . length ; j ++ ) { var fragment = sortedFragments [ j ] ; var data = fragment . data ; var times_PMILLIS = fragment . times_PMILLIS ; for ( var k = 0 ; k < data . length ; k ++ , index += 2 ) { xys [ index ] = timeAxis . vAtTime ( times_PMILLIS [ k ] ) ; xys [ index + 1 ] = data [ k ] ; } } var lineColor = hasval ( timeseries . lineColor ) ? timeseries . lineColor : defaultColor ; u_Color . setData ( gl , lineColor ) ; var lineThickness = hasval ( timeseries . lineThickness ) ? timeseries . lineThickness : defaultThickness ; gl . lineWidth ( lineThickness ) ; xysBuffer . setData ( xys . subarray ( 0 , index ) ) ; a_Position . setDataAndEnable ( gl , xysBuffer , 2 , GL . FLOAT ) ; if ( timeseries . uiHint == 'lines' || timeseries . uiHint == 'lines-and-points' || timeseries . uiHint == undefined ) { gl . drawArrays ( GL . LINE_STRIP , 0 , size / 2 ) ; } if ( timeseries . uiHint == 'points' || timeseries . uiHint == 'lines-and-points' ) { var pointColor = hasval ( timeseries . pointColor ) ? timeseries . pointColor : defaultColor ; u_Color . setData ( gl , pointColor ) ; u_PointSize . setData ( gl , timeseries . pointSize ) ; gl . drawArrays ( GL . POINTS , 0 , size / 2 ) ; } } else if ( timeseries . uiHint == 'bars' ) { if ( totalSize >= 2 ) { var baseline = timeseries . baseline ; var size = ( totalSize - 1 ) * 12 ; xys = ensureCapacityFloat32 ( xys , size ) ; var index = 0 ; for ( var j = 0 ; j < sortedFragments . length ; j ++ ) { var fragment = sortedFragments [ j ] ; var data = fragment . data ; var times_PMILLIS = fragment . times_PMILLIS ; for ( var k = 0 ; k < data . length - 1 ; k ++ ) { var x1 = timeAxis . vAtTime ( times_PMILLIS [ k ] ) ; var y1 = data [ k ] ; var x2 = timeAxis . vAtTime ( times_PMILLIS [ k + 1 ] ) ; var y2 = data [ k + 1 ] ; index = putQuadXys ( xys , index , x1 , x2 , y1 , baseline ) ; } } var lineColor = hasval ( timeseries . lineColor ) ? timeseries . lineColor : defaultColor ; u_Color . setData ( gl , lineColor ) ; xysBuffer . setData ( xys . subarray ( 0 , index ) ) ; a_Position . setDataAndEnable ( gl , xysBuffer , 2 , GL . FLOAT ) ; gl . drawArrays ( GL . TRIANGLES , 0 , size / 2 ) ; } } else if ( timeseries . uiHint == 'area' ) { var baseline = timeseries . baseline ; var size = totalSize * 4 ; xys = ensureCapacityFloat32 ( xys , size ) ; var index = 0 ; for ( var j = 0 ; j < sortedFragments . length ; j ++ ) { var fragment = sortedFragments [ j ] ; var data = fragment . data ; var times_PMILLIS = fragment . times_PMILLIS ; for ( var k = 0 ; k < data . length ; k ++ , index += 4 ) { var x1 = timeAxis . vAtTime ( times_PMILLIS [ k ] ) ; var y1 = data [ k ] ; xys [ index ] = x1 ; xys [ index + 1 ] = baseline ; xys [ index + 2 ] = x1 ; xys [ index + 3 ] = y1 ; } } var lineColor = hasval ( timeseries . lineColor ) ? timeseries . lineColor : defaultColor ; u_Color . setData ( gl , lineColor ) ; xysBuffer . setData ( xys . subarray ( 0 , index ) ) ; a_Position . setDataAndEnable ( gl , xysBuffer , 2 , GL . FLOAT ) ; gl . drawArrays ( GL . TRIANGLE_STRIP , 0 , size / 2 ) ; } if ( selection . hoveredTimeseries . fragment && timeseries . fragmentGuids . hasValue ( selection . hoveredTimeseries . fragment . fragmentGuid ) ) { if ( timeseries . uiHint == 'area' || timeseries . uiHint == 'lines' || timeseries . uiHint == 'points' || timeseries . uiHint == 'lines-and-points' || timeseries . uiHint == undefined ) { var size = 8 ; xys = ensureCapacityFloat32 ( xys , size ) ; var vppx = timeAxis . vSize / viewport . w ; var vppy = dataAxis . vSize / viewport . h ; var highlightSize = hasval ( timeseries . pointSize ) ? timeseries . pointSize : 5 ; var bufferx = ( highlightSize / 2 ) * vppx ; var buffery = ( highlightSize / 2 ) * vppy ; var fragment = selection . hoveredTimeseries . fragment ; var y = selection . hoveredTimeseries . data ; var x = timeAxis . vAtTime ( selection . hoveredTimeseries . times_PMILLIS ) ; xys [ 0 ] = x - bufferx ; xys [ 1 ] = y - buffery ; xys [ 2 ] = x + bufferx ; xys [ 3 ] = y - buffery ; xys [ 4 ] = x + bufferx ; xys [ 5 ] = y + buffery ; xys [ 6 ] = x - bufferx ; xys [ 7 ] = y + buffery ; var color = hasval ( timeseries . pointColor ) ? timeseries . pointColor : defaultColor ; u_Color . setData ( gl , darker ( color , 0.8 ) ) ; xysBuffer . setData ( xys . subarray ( 0 , size ) ) ; a_Position . setDataAndEnable ( gl , xysBuffer , 2 , GL . FLOAT ) ; gl . drawArrays ( GL . LINE_LOOP , 0 , size / 2 ) ; } else if ( timeseries . uiHint == 'bars' ) { var size = 8 ; xys = ensureCapacityFloat32 ( xys , size ) ; var fragment = selection . hoveredTimeseries . fragment ; var index = selection . hoveredTimeseries . index ; if ( index < fragment . data . length ) { var x1 = timeAxis . vAtTime ( fragment . times_PMILLIS [ index ] ) ; var y1 = fragment . data [ index ] ; var x2 = timeAxis . vAtTime ( fragment . times_PMILLIS [ index + 1 ] ) ; var y2 = fragment . data [ index + 1 ] ; xys [ 0 ] = x1 ; xys [ 1 ] = y1 ; xys [ 2 ] = x2 ; xys [ 3 ] = y1 ; xys [ 4 ] = x2 ; xys [ 5 ] = baseline ; xys [ 6 ] = x1 ; xys [ 7 ] = baseline ; var color = hasval ( timeseries . lineColor ) ? timeseries . lineColor : defaultColor ; u_Color . setData ( gl , darker ( color , 0.8 ) ) ; xysBuffer . setData ( xys . subarray ( 0 , size ) ) ; a_Position . setDataAndEnable ( gl , xysBuffer , 2 , GL . FLOAT ) ; gl . drawArrays ( GL . LINE_LOOP , 0 , size / 2 ) ; } } } } a_Position . disable ( gl ) ; program . endUse ( gl ) ; } ; } ; } }