import { BufferAttribute , BufferGeometry , DoubleSide , InstancedBufferAttribute , InstancedBufferGeometry , InstancedMesh , Material , RawShaderMaterial , } from 'three' ; import { Primitive , DataStream , AlarmsConfig } from '../../../utils/dataTypes' ; import statusVert from './status.vert' ; import statusFrag from './status.frag' ; import { WriteableBufferAttribute , WriteableInstancedBufferAttribute } from '../../sc-webgl-context/types' ; import { numDataPoints , vertices } from '../sc-webgl-base-chart/utils' ; import { getCSSColorByString } from '../common/getCSSColorByString' ; import { STATUS_MARGIN_TOP_PX , DEFAULT_STATUS_BAR_COLOR , HEIGHT } from './constants' ; import { getBreachedThreshold } from '../common/annotations/utils' ; import { getDistanceFromDuration } from '../common/getDistanceFromDuration' ; import { Threshold , ThresholdOptions } from '../common/types' ; type StatusBufferGeometry = BufferGeometry & { attributes : { position : WriteableBufferAttribute ; status : WriteableInstancedBufferAttribute ; color : WriteableInstancedBufferAttribute ; } ; } ; type StatusChartLineMaterial = Material & { uniforms : { devicePixelRatio : { value : number ; } ; } ; } ; export type StatusChartStatusMesh = InstancedMesh & { geometry : StatusBufferGeometry ; material : StatusChartLineMaterial ; } ; export const NUM_POSITION_COMPONENTS = 2 ; export const NUM_STATUS_COMPONENTS = 4 ; const NUM_COLOR_COMPONENTS = 3 ; const numStatuses = ( streamVertexSets ) : number => streamVertexSets . reduce ( ( totalStatuses , streamVertexSet ) => { const streamStatuses = Math . max ( streamVertexSet . length , 0 ) ; return totalStatuses + streamStatuses ; } , 0 ) ; /**
 * Returns clip space width of the bar
 */ const getWidth = ( { nextX , currX , toClipSpace , alarms , } ) => { const expires = alarms ? alarms . expires : undefined ; if ( expires != null ) { const maxWidth = getDistanceFromDuration ( toClipSpace , expires ) ; if ( nextX == null ) { return maxWidth ; } return Math . min ( getDistanceFromDuration ( toClipSpace , nextX - currX ) , maxWidth ) ; } if ( nextX != null ) { return getDistanceFromDuration ( toClipSpace , nextX - currX ) ; } return getDistanceFromDuration ( toClipSpace , Date . now ( ) - currX ) ; } ; const updateMesh = ( { dataStreams , mesh , toClipSpace , thresholds , thresholdOptions , chartSize , alarms , } ) => { const streamVertexSets = dataStreams . map ( stream => vertices ( stream , stream . resolution ) ) ; mesh . count = numStatuses ( streamVertexSets ) ; const { geometry } = mesh ; const { color , status } = geometry . attributes ; let statusIndex = 0 ; let colorIndex = 0 ; /** Layout */ const rowHeight = HEIGHT / dataStreams . length ; const margin = STATUS_MARGIN_TOP_PX / chartSize . height ; const vizHeight = rowHeight - margin ; streamVertexSets . forEach ( ( streamVertexSet , setIndex ) => { streamVertexSet . forEach ( ( currVertex , v ) => { const nextVertex = streamVertexSet [ v + 1 ] ; const [ nextX = undefined ] = nextVertex || [ ] ; const [ currX , currY ] = currVertex ; /**
             * Color Buffer Construction
             */ const breachedThreshold = getBreachedThreshold ( currY , thresholds ) ; if ( breachedThreshold == null || ! thresholdOptions . showColor ) { const [ r , g , b ] = DEFAULT_STATUS_BAR_COLOR ; color . array [ colorIndex ] = r ; color . array [ colorIndex + 1 ] = g ; color . array [ colorIndex + 2 ] = b ; } else { const [ rr , gg , bb ] = getCSSColorByString ( breachedThreshold . color ) ; color . array [ colorIndex ] = rr ; color . array [ colorIndex + 1 ] = gg ; color . array [ colorIndex + 2 ] = bb ; } colorIndex += NUM_COLOR_COMPONENTS ; /**
             * Position Buffer Construction
             *
             * The 'y range' varies from 0 to 1 (HEIGHT). We need to convert pixels over to clip space
             */ status . array [ statusIndex ] = toClipSpace ( currX ) ; status . array [ statusIndex + 1 ] = HEIGHT - rowHeight * ( setIndex + 1 ) ; status . array [ statusIndex + 2 ] = getWidth ( { currX , nextX , toClipSpace , alarms } ) ; status . array [ statusIndex + 3 ] = vizHeight ; statusIndex += NUM_STATUS_COMPONENTS ; } ) ; } ) ; status . needsUpdate = true ; color . needsUpdate = true ; } ; const unitSquare = [ 0 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 1 ] ; const initializeGeometry = ( geometry , bufferSize ) => { geometry . setAttribute ( 'position' , new BufferAttribute ( new Float32Array ( unitSquare ) , NUM_POSITION_COMPONENTS ) ) ; geometry . setAttribute ( 'status' , new InstancedBufferAttribute ( new Float32Array ( bufferSize * NUM_STATUS_COMPONENTS ) , NUM_STATUS_COMPONENTS , false ) ) ; geometry . setAttribute ( 'color' , new InstancedBufferAttribute ( new Uint8Array ( bufferSize * NUM_COLOR_COMPONENTS ) , NUM_COLOR_COMPONENTS , true ) ) ; } ; export const statusMesh = ( { alarms , dataStreams , toClipSpace , bufferFactor , minBufferSize , thresholdOptions , thresholds , chartSize , } ) => { const instGeo = ( new InstancedBufferGeometry ( ) as unknown ) as StatusBufferGeometry ; const bufferSize = Math . max ( minBufferSize , numDataPoints ( dataStreams ) * bufferFactor ) ; initializeGeometry ( instGeo , bufferSize ) ; /**
     * Create Status Mesh
     *
     * The representation of the statuses on a status chart.
     *
     * Utilizes an instance of a single unit square, which then gets
     * stretched and transposed across the canvas.
     */ const statusChartMaterial = new RawShaderMaterial ( { vertexShader : statusVert , fragmentShader : statusFrag , side : DoubleSide , transparent : false , } ) ; const mesh = < StatusChartStatusMesh > new InstancedMesh ( instGeo , statusChartMaterial , bufferSize ) ; updateMesh ( { dataStreams , mesh , toClipSpace , thresholds , thresholdOptions , chartSize , alarms , } ) ; mesh . frustumCulled = false ; return mesh ; } ; export const updateStatusMesh = ( { alarms , statuses , dataStreams , toClipSpace , thresholdOptions , thresholds , chartSize , hasDataChanged , hasAnnotationChanged , hasSizeChanged , } ) => { if ( hasDataChanged || hasAnnotationChanged || hasSizeChanged ) { updateMesh ( { dataStreams , mesh : statuses , toClipSpace , thresholds , thresholdOptions , chartSize , alarms , } ) ; } } ;