import { BufferAttribute , BufferGeometry , DoubleSide , InstancedBufferAttribute , InstancedBufferGeometry , InstancedMesh , Material , RawShaderMaterial , } from 'three' ; import barVert from './bar.vert' ; import barFrag from './bar.frag' ; import { WriteableBufferAttribute , WriteableInstancedBufferAttribute } from '../../sc-webgl-context/types' ; import { numDataPoints , vertices } from '../sc-webgl-base-chart/utils' ; import { getCSSColorByString } from '../common/getCSSColorByString' ; import { getBarWidth } from './displayLogic' ; import { getBreachedThreshold } from '../common/annotations/utils' ; import { isNumberDataStream } from '../../../utils/predicates' ; import { DataStream , Primitive } from '../../../utils/dataTypes' ; import { Threshold , ThresholdOptions } from '../common/types' ; type BarBufferGeometry = BufferGeometry & { attributes : { position : WriteableBufferAttribute ; bar : WriteableInstancedBufferAttribute ; color : WriteableInstancedBufferAttribute ; } ; } ; type BarChartLineMaterial = Material & { uniforms : { width : { value : number ; } ; devicePixelRatio : { value : number ; } ; } ; } ; export type BarChartBarMesh = InstancedMesh & { geometry : BarBufferGeometry ; material : BarChartLineMaterial ; } ; export const NUM_POSITION_COMPONENTS = 2 ; const NUM_COLOR_COMPONENTS = 3 ; const numBars = ( streamVertexSets ) : number => { return streamVertexSets . reduce ( ( totalBars , streamVertexSet ) => totalBars + streamVertexSet . length , 0 ) ; } ; const getUniformWidth = < T extends Primitive > ( dataStreams , toClipSpace ) : number => { if ( dataStreams . length === 0 ) { return 0 ; } const { resolution } = dataStreams [ 0 ] ; return getBarWidth ( { toClipSpace , numDataStreams : dataStreams . length , resolution , } ) ; } ; const updateMesh = ( { dataStreams , mesh , toClipSpace , thresholds , thresholdOptions , } ) => { const streamVertexSets = dataStreams . filter ( isNumberDataStream ) . map ( stream => vertices ( stream , stream . resolution ) ) ; mesh . count = numBars ( streamVertexSets ) ; const { geometry } = mesh ; const { color , bar } = geometry . attributes ; let positionIndex = 0 ; let colorIndex = 0 ; streamVertexSets . forEach ( ( streamVertexSet , setIndex ) => { streamVertexSet . forEach ( currVertex => { const [ currX , currY , r , g , b ] = currVertex ; /**
             * Subtracting setIndex * getUniformWidth(dataStreams, toClipSpace) because with each new
             * data stream, we want to render it side by side on the left side.
             */ bar . array [ positionIndex ] = toClipSpace ( currX ) - setIndex * getUniformWidth ( dataStreams , toClipSpace ) ; bar . array [ positionIndex + 1 ] = currY ; const breachedThreshold = getBreachedThreshold ( currY , thresholds ) ; if ( breachedThreshold == null || ! thresholdOptions . showColor ) { 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 ; positionIndex += NUM_POSITION_COMPONENTS ; } ) ; } ) ; bar . 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 ( 'bar' , new InstancedBufferAttribute ( new Float32Array ( bufferSize * NUM_POSITION_COMPONENTS ) , NUM_POSITION_COMPONENTS , false ) ) ; geometry . setAttribute ( 'color' , new InstancedBufferAttribute ( new Uint8Array ( bufferSize * NUM_COLOR_COMPONENTS ) , NUM_COLOR_COMPONENTS , true ) ) ; } ; export const barMesh = ( { dataStreams , toClipSpace , bufferFactor , minBufferSize , thresholdOptions , thresholds , } ) => { const instGeo = ( new InstancedBufferGeometry ( ) as unknown ) as BarBufferGeometry ; const bufferSize = Math . max ( minBufferSize , numDataPoints ( dataStreams ) * bufferFactor ) ; initializeGeometry ( instGeo , bufferSize ) ; /**
     * Create Bar Mesh
     *
     * The representation of the bars on a bar chart.
     *
     * Utilizes an instance of a single unit square, which then gets
     * stretched and transposed across the canvas.
     */ const barChartMaterial = new RawShaderMaterial ( { vertexShader : barVert , fragmentShader : barFrag , side : DoubleSide , transparent : false , uniforms : { width : { value : getUniformWidth ( dataStreams , toClipSpace ) , } , } , } ) ; const mesh = < BarChartBarMesh > new InstancedMesh ( instGeo , barChartMaterial , bufferSize ) ; updateMesh ( { dataStreams , mesh , toClipSpace , thresholds , thresholdOptions } ) ; mesh . frustumCulled = false ; return mesh ; } ; export const updateBarMesh = ( { bars , dataStreams , toClipSpace , hasDataChanged , thresholdOptions , thresholds , } ) => { if ( hasDataChanged ) { bars . material . uniforms . width . value = getUniformWidth ( dataStreams , toClipSpace ) ; updateMesh ( { dataStreams , mesh : bars , toClipSpace , thresholds , thresholdOptions } ) ; } } ;