@@ -8,7 +8,9 @@ const { lightningChart, LUT, ColorHSV, PalettedFill, emptyLine, AxisScrollStrate
88const historyMs = 27 * 1000
99// Sampling rate as samples per second.
1010const sampleRateHz = 35
11- const sampleIntervalMs = 1000 / sampleRateHz
11+ // Minimum time step that can be displayed by the heatmap. In this example, set to half of average interval between samples. In normal applications you can set this to some comfortably small value.
12+ // Smaller value means more precision but more RAM and GPU memory usage.
13+ const heatmapMinTimeStepMs = ( 0.5 * 1000 ) / sampleRateHz
1214
1315// Create empty dashboard and charts.
1416// NOTE: Using `Dashboard` is no longer recommended for new applications. Find latest recommendations here: https://lightningchart.com/js-charts/docs/basic-topics/grouping-charts/
@@ -56,20 +58,18 @@ fetch(new URL(document.head.baseURI).origin + new URL(document.head.baseURI).pat
5658 } ,
5759 ]
5860
59- channelList = channelList . map ( ( channel ) => {
61+ channelList = channelList . map ( ( channel , iii ) => {
6062 const rows = channel . data [ 0 ] . length
6163 const chart2D = dashboard
6264 . createChartXY ( {
6365 columnIndex : channel . columnIndex ,
6466 rowIndex : 0 ,
6567 } )
6668 . setTitle ( `${ channel . name } | 2D audio spectrogram` )
67- chart2D
68- . getDefaultAxisX ( )
69- . setTickStrategy ( AxisTickStrategies . Time )
69+ chart2D . axisX
7070 . setScrollStrategy ( AxisScrollStrategies . progressive )
7171 . setDefaultInterval ( ( state ) => ( { end : state . dataMax , start : ( state . dataMax ?? 0 ) - historyMs , stopAxisAfter : false } ) )
72- chart2D . getDefaultAxisY ( ) . setTitle ( 'Frequency' ) . setUnits ( 'Hz' )
72+ chart2D . axisY . setTitle ( 'Frequency' ) . setUnits ( 'Hz' )
7373
7474 const chart3D = dashboard
7575 . createChart3D ( {
@@ -78,13 +78,10 @@ fetch(new URL(document.head.baseURI).origin + new URL(document.head.baseURI).pat
7878 } )
7979 . setTitle ( `${ channel . name } | 3D audio spectrogram` )
8080
81- chart3D
82- . getDefaultAxisX ( )
83- . setTickStrategy ( AxisTickStrategies . Time )
81+ chart3D . axisX
8482 . setScrollStrategy ( AxisScrollStrategies . progressive )
8583 . setDefaultInterval ( ( state ) => ( { end : state . dataMax , start : ( state . dataMax ?? 0 ) - historyMs , stopAxisAfter : false } ) )
86- chart3D
87- . getDefaultAxisY ( )
84+ chart3D . axisY
8885 . setTitle ( 'Intensity' )
8986 . setUnits ( 'dB' )
9087 . setTickStrategy ( AxisTickStrategies . Numeric , ( ticks ) =>
@@ -97,46 +94,67 @@ fetch(new URL(document.head.baseURI).origin + new URL(document.head.baseURI).pat
9794 scrollDimension : 'columns' ,
9895 resolution : rows ,
9996 } )
100- . setStep ( { x : sampleIntervalMs , y : rowStep } )
97+ . setStep ( { x : heatmapMinTimeStepMs , y : rowStep } )
10198 . setFillStyle ( new PalettedFill ( { lut } ) )
10299 . setWireframeStyle ( emptyLine )
103100 . setDataCleaning ( { maxDataPointCount : 10000 } )
104101
105102 const surfaceSeries3D = chart3D
106103 . addSurfaceScrollingGridSeries ( {
107104 scrollDimension : 'columns' ,
108- columns : Math . ceil ( historyMs / sampleIntervalMs ) ,
105+ columns : Math . ceil ( historyMs / heatmapMinTimeStepMs ) ,
109106 rows,
110107 } )
111- . setStep ( { x : sampleIntervalMs , z : rowStep } )
108+ . setStep ( { x : heatmapMinTimeStepMs , z : rowStep } )
112109 . setFillStyle ( new PalettedFill ( { lut, lookUpProperty : 'y' } ) )
113110 . setWireframeStyle ( emptyLine )
114111
115112 return { ...channel , chart2D, chart3D, heatmapSeries2D, surfaceSeries3D }
116113 } )
117114
118- // Setup infinite streaming from static data set.
119- let tStart = window . performance . now ( )
120- let pushedDataCount = 0
121- const streamData = ( ) => {
122- const tNow = window . performance . now ( )
123- // NOTE: This code is for example purposes (streaming stable data rate without destroying browser when switching tabs etc.)
124- // In real use cases, data should be pushed in when it comes.
125- const shouldBeDataPointsCount = Math . floor ( ( sampleRateHz * ( tNow - tStart ) ) / 1000 )
126- const newDataPointsCount = Math . min ( shouldBeDataPointsCount - pushedDataCount , 100 ) // Add max 100 samples per frame into a series. This prevents massive performance spikes when switching tabs for long times
127- if ( newDataPointsCount > 0 ) {
128- channelList . forEach ( ( channel , i ) => {
129- const newDataPoints = [ ]
130- for ( let iDp = 0 ; iDp < newDataPointsCount ; iDp ++ ) {
131- const iData = ( pushedDataCount + iDp ) % channel . data . length
132- const sample = channel . data [ iData ]
133- newDataPoints . push ( sample )
134- }
135- channel . heatmapSeries2D . addIntensityValues ( newDataPoints )
136- channel . surfaceSeries3D . addValues ( { yValues : newDataPoints } )
115+ let tFirstSample
116+ const handleIncomingData = ( channel , timestamp , sample ) => {
117+ if ( ! tFirstSample ) {
118+ tFirstSample = timestamp
119+ channelList . forEach ( ( ch ) => {
120+ ch . chart2D . axisX . setTickStrategy ( AxisTickStrategies . DateTime , ( strategy ) =>
121+ strategy . setDateOrigin ( new Date ( tFirstSample ) ) ,
122+ )
123+ ch . chart3D . axisX . setTickStrategy ( AxisTickStrategies . DateTime , ( strategy ) =>
124+ strategy . setDateOrigin ( new Date ( tFirstSample ) ) ,
125+ )
137126 } )
138- pushedDataCount += newDataPointsCount
139127 }
128+ // Calculate sample index from timestamp to place sample in correct location in heatmap.
129+ const iSample = Math . round ( ( timestamp - tFirstSample ) / heatmapMinTimeStepMs )
130+ channel . heatmapSeries2D . invalidateIntensityValues ( {
131+ iSample,
132+ values : [ sample ] ,
133+ } )
134+ channel . surfaceSeries3D . invalidateValues ( {
135+ iSample,
136+ yValues : [ sample ] ,
137+ } )
138+ }
139+
140+ // Setup infinite streaming from static data set.
141+ let iData = 0
142+ let tPrev = Date . now ( )
143+ let dModulus = 0
144+ const streamData = ( ) => {
145+ const tNow = Date . now ( )
146+ let addDataPointCount = ( ( tNow - tPrev ) * sampleRateHz ) / 1000 + dModulus
147+ dModulus = addDataPointCount % 1
148+ addDataPointCount = Math . floor ( addDataPointCount )
149+ channelList . forEach ( ( channel ) => {
150+ for ( let i = 0 ; i < addDataPointCount ; i += 1 ) {
151+ const timestamp = tPrev + ( ( i + 1 ) / addDataPointCount ) * ( tNow - tPrev )
152+ const sample = channel . data [ ( iData + i ) % channel . data . length ]
153+ handleIncomingData ( channel , timestamp , sample )
154+ }
155+ } )
156+ iData += addDataPointCount
157+ tPrev = tNow
140158 requestAnimationFrame ( streamData )
141159 }
142160 streamData ( )
0 commit comments