LTTB Decimation (#8468)

* LTTB Decimation
* Lint fixes
This commit is contained in:
Evert Timberg 2021-02-21 09:15:45 -05:00 committed by GitHub
parent def8d25d4b
commit 5c9e1d578c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 3 deletions

View File

@ -12,13 +12,19 @@ Namespace: `options.plugins.decimation`, the global options for the plugin are d
| ---- | ---- | ------- | -----------
| `enabled` | `boolean` | `true` | Is decimation enabled?
| `algorithm` | `string` | `'min-max'` | Decimation algorithm to use. See the [more...](#decimation-algorithms)
| `samples` | `number` | | If the `'lttb'` algorithm is used, this is the number of samples in the output dataset. Defaults to the canvas width to pick 1 sample per pixel.
## Decimation Algorithms
Decimation algorithm to use for data. Options are:
* `'lttb'`
* `'min-max'`
### Largest Triangle Three Bucket (LTTB) Decimation
[LTTB](https://github.com/sveinn-steinarsson/flot-downsample) decimation reduces the number of data points significantly. This is most useful for showing trends in data using only a few data points.
### Min/Max Decimation
[Min/max](https://digital.ni.com/public.nsf/allkb/F694FFEEA0ACF282862576020075F784) decimation will preserve peaks in your data but could require up to 4 points for each pixel. This type of decimation would work well for a very noisy signal where you need to see data peaks.

View File

@ -1,5 +1,73 @@
import {isNullOrUndef, resolve} from '../helpers';
function lttbDecimation(data, availableWidth, options) {
/**
* Implementation of the Largest Triangle Three Buckets algorithm.
*
* This implementation is based on the original implementation by Sveinn Steinarsson
* in https://github.com/sveinn-steinarsson/flot-downsample/blob/master/jquery.flot.downsample.js
*
* The original implementation is MIT licensed.
*/
const samples = options.samples || availableWidth;
const decimated = [];
const bucketWidth = (data.length - 2) / (samples - 2);
let sampledIndex = 0;
let a = 0;
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);
const avgRangeLength = avgRangeEnd - avgRangeStart;
for (j = avgRangeStart; j < avgRangeEnd; j++) {
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;
const {x: pointAx, y: pointAy} = data[a];
// Note that this is changed from the original algorithm which initializes these
// values to 1. The reason for this change is that if the area is small, nextA
// would never be set and thus a crash would occur in the next loop as `a` would become
// `undefined`. Since the area is always positive, but could be 0 in the case of a flat trace,
// initializing with a negative number is the correct solution.
maxArea = area = -1;
for (j = rangeOffs; j < rangeTo; j++) {
area = 0.5 * Math.abs(
(pointAx - avgX) * (data[j].y - pointAy) -
(pointAx - data[j].x) * (avgY - pointAy)
);
if (area > maxArea) {
maxArea = area;
maxAreaPoint = data[j];
nextA = j;
}
}
decimated[sampledIndex++] = maxAreaPoint;
a = nextA;
}
// Include the last point
decimated[sampledIndex++] = data[data.length - 1];
return decimated;
}
function minMaxDecimation(data, availableWidth) {
let avgX = 0;
let countX = 0;
@ -141,6 +209,9 @@ export default {
// Point the chart to the decimated data
let decimated;
switch (options.algorithm) {
case 'lttb':
decimated = lttbDecimation(data, availableWidth, options);
break;
case 'min-max':
decimated = minMaxDecimation(data, availableWidth);
break;

16
types/index.esm.d.ts vendored
View File

@ -1920,14 +1920,24 @@ export class BasicPlatform extends BasePlatform {}
export class DomPlatform extends BasePlatform {}
export declare enum DecimationAlgorithm {
lttb = 'lttb',
minmax = 'min-max',
}
export interface DecimationOptions {
interface BaseDecimationOptions {
enabled: boolean;
algorithm: DecimationAlgorithm;
}
interface LttbDecimationOptions extends BaseDecimationOptions {
algorithm: DecimationAlgorithm.lttb;
samples?: number;
}
interface MinMaxDecimationOptions extends BaseDecimationOptions {
algorithm: DecimationAlgorithm.minmax;
}
export type DecimationOptions = LttbDecimationOptions | MinMaxDecimationOptions;
export const Filler: Plugin;
export interface FillerOptions {
propagate: boolean;