Filtering data before decimation (#8843)

* Filtering data before decimation

Using only points between the currently displayed x-axis for the decimation algorithm.
Allows better resolution, especially if using a zoom

If data are outside range, they will not be displayed, hence the line graph will not show the trend at extremities

* Fix LTTB algorithm

* Adding test file

* Simplifying count algorithm for decimation plugin
This commit is contained in:
Nico-DF
2021-04-07 22:40:45 +02:00
committed by GitHub
parent 82d42bd799
commit ba3320ef19
3 changed files with 198 additions and 19 deletions

View File

@@ -1,6 +1,6 @@
import {isNullOrUndef, resolve} from '../helpers';
import {_limitValue, _lookupByKey, isNullOrUndef, resolve} from '../helpers';
function lttbDecimation(data, availableWidth, options) {
function lttbDecimation(data, start, count, availableWidth, options) {
/**
* Implementation of the Largest Triangle Three Buckets algorithm.
*
@@ -10,32 +10,43 @@ function lttbDecimation(data, availableWidth, options) {
* The original implementation is MIT licensed.
*/
const samples = options.samples || availableWidth;
// There is less points than the threshold, returning the whole array
if (samples >= count) {
return data.slice(start, start + count);
}
const decimated = [];
const bucketWidth = (data.length - 2) / (samples - 2);
const bucketWidth = (count - 2) / (samples - 2);
let sampledIndex = 0;
let a = 0;
const endIndex = start + count - 1;
// Starting from offset
let a = start;
let i, maxAreaPoint, maxArea, area, nextA;
decimated[sampledIndex++] = data[a];
for (i = 0; i < samples - 2; i++) {
let avgX = 0;
let avgY = 0;
let j;
const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1;
const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, data.length);
// Adding offset
const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
const avgRangeLength = avgRangeEnd - avgRangeStart;
for (j = avgRangeStart; j < avgRangeEnd; j++) {
avgX = data[j].x;
avgY = data[j].y;
avgX += data[j].x;
avgY += data[j].y;
}
avgX /= avgRangeLength;
avgY /= avgRangeLength;
const rangeOffs = Math.floor(i * bucketWidth) + 1;
const rangeTo = Math.floor((i + 1) * bucketWidth) + 1;
// Adding offset
const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
const rangeTo = Math.floor((i + 1) * bucketWidth) + 1 + start;
const {x: pointAx, y: pointAy} = data[a];
// Note that this is changed from the original algorithm which initializes these
@@ -63,22 +74,23 @@ function lttbDecimation(data, availableWidth, options) {
}
// Include the last point
decimated[sampledIndex++] = data[data.length - 1];
decimated[sampledIndex++] = data[endIndex];
return decimated;
}
function minMaxDecimation(data, availableWidth) {
function minMaxDecimation(data, start, count, availableWidth) {
let avgX = 0;
let countX = 0;
let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
const decimated = [];
const endIndex = start + count - 1;
const xMin = data[0].x;
const xMax = data[data.length - 1].x;
const xMin = data[start].x;
const xMax = data[endIndex].x;
const dx = xMax - xMin;
for (i = 0; i < data.length; ++i) {
for (i = start; i < start + count; ++i) {
point = data[i];
x = (point.x - xMin) / dx * availableWidth;
y = point.y;
@@ -152,6 +164,27 @@ function cleanDecimatedData(chart) {
});
}
function getStartAndCountOfVisiblePointsSimplified(meta, points) {
const pointCount = points.length;
let start = 0;
let count;
const {iScale} = meta;
const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
if (minDefined) {
start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
}
if (maxDefined) {
count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
} else {
count = pointCount - start;
}
return {start, count};
}
export default {
id: 'decimation',
@@ -196,7 +229,8 @@ export default {
return;
}
if (data.length <= 4 * availableWidth) {
let {start, count} = getStartAndCountOfVisiblePointsSimplified(meta, data);
if (count <= 4 * availableWidth) {
// No decimation is required until we are above this threshold
return;
}
@@ -223,10 +257,10 @@ export default {
let decimated;
switch (options.algorithm) {
case 'lttb':
decimated = lttbDecimation(data, availableWidth, options);
decimated = lttbDecimation(data, start, count, availableWidth, options);
break;
case 'min-max':
decimated = minMaxDecimation(data, availableWidth);
decimated = minMaxDecimation(data, start, count, availableWidth);
break;
default:
throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);