UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

82 lines (81 loc) 4.29 kB
import { divMod } from './utils'; export const resampleWaveformData = (waveformData, amplitudesCount) => waveformData.length === amplitudesCount ? waveformData : waveformData.length > amplitudesCount ? downSample(waveformData, amplitudesCount) : upSample(waveformData, amplitudesCount); /** * The downSample function uses the Largest-Triangle-Three-Buckets (LTTB) algorithm. * See the thesis Downsampling Time Series for Visual Representation by Sveinn Steinarsson for more (https://skemman.is/bitstream/1946/15343/3/SS_MSthesis.pdf) * @param data * @param targetOutputSize */ export function downSample(data, targetOutputSize) { if (data.length <= targetOutputSize || targetOutputSize === 0) { return data; } if (targetOutputSize === 1) return [mean(data)]; const result = []; // bucket size adjusted due to the fact that the first and the last item in the original data array is kept in target output const bucketSize = (data.length - 2) / (targetOutputSize - 2); let lastSelectedPointIndex = 0; result.push(data[lastSelectedPointIndex]); // Always add the first point let maxAreaPoint, maxArea, triangleArea; for (let bucketIndex = 1; bucketIndex < targetOutputSize - 1; bucketIndex++) { const previousBucketRefPoint = data[lastSelectedPointIndex]; const nextBucketMean = getNextBucketMean(data, bucketIndex, bucketSize); const currentBucketStartIndex = Math.floor((bucketIndex - 1) * bucketSize) + 1; const nextBucketStartIndex = Math.floor(bucketIndex * bucketSize) + 1; const countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex; maxArea = triangleArea = -1; for (let currentPointIndex = currentBucketStartIndex; currentPointIndex < nextBucketStartIndex; currentPointIndex++) { const countUnitsBetweenAtoB = Math.abs(currentPointIndex - currentBucketStartIndex) + 1; const countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB; const currentPointValue = data[currentPointIndex]; triangleArea = triangleAreaHeron(triangleBase(Math.abs(previousBucketRefPoint - currentPointValue), countUnitsBetweenAtoB), triangleBase(Math.abs(currentPointValue - nextBucketMean), countUnitsBetweenBtoC), triangleBase(Math.abs(previousBucketRefPoint - nextBucketMean), countUnitsBetweenAtoC)); if (triangleArea > maxArea) { maxArea = triangleArea; maxAreaPoint = data[currentPointIndex]; lastSelectedPointIndex = currentPointIndex; } } if (typeof maxAreaPoint !== 'undefined') result.push(maxAreaPoint); } result.push(data[data.length - 1]); // Always add the last point return result; } const triangleAreaHeron = (a, b, c) => { const s = (a + b + c) / 2; return Math.sqrt(s * (s - a) * (s - b) * (s - c)); }; const triangleBase = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); const mean = (values) => values.reduce((acc, value) => acc + value, 0) / values.length; const getNextBucketMean = (data, currentBucketIndex, bucketSize) => { const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1; let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1; nextNextBucketStartIndex = nextNextBucketStartIndex < data.length ? nextNextBucketStartIndex : data.length; return mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex)); }; export const upSample = (values, targetSize) => { if (!values.length) { console.warn('Cannot extend empty array of amplitudes.'); return values; } if (values.length > targetSize) { console.warn('Requested to extend the waveformData that is longer than the target list size'); return values; } if (targetSize === values.length) return values; // eslint-disable-next-line prefer-const let [bucketSize, remainder] = divMod(targetSize, values.length); const result = []; for (let i = 0; i < values.length; i++) { const extra = remainder && remainder-- ? 1 : 0; result.push(...Array(bucketSize + extra).fill(values[i])); } return result; };