Skip to content

Commit

Permalink
LTTB Decimation (#8468)
Browse files Browse the repository at this point in the history
* LTTB Decimation
* Lint fixes
  • Loading branch information
etimberg authored Feb 21, 2021
1 parent def8d25 commit 5c9e1d5
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 3 deletions.
6 changes: 6 additions & 0 deletions docs/docs/configuration/decimation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
71 changes: 71 additions & 0 deletions src/plugins/plugin.decimation.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 13 additions & 3 deletions types/index.esm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 5c9e1d5

Please sign in to comment.