From: Evert Timberg Date: Sun, 21 Feb 2021 14:15:45 +0000 (-0500) Subject: LTTB Decimation (#8468) X-Git-Tag: v3.0.0-beta.12~37 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5c9e1d578c4611652f6d7e96c8982b5529667d91;p=thirdparty%2FChart.js.git LTTB Decimation (#8468) * LTTB Decimation * Lint fixes --- diff --git a/docs/docs/configuration/decimation.md b/docs/docs/configuration/decimation.md index 3f741b75d..a9bfb4fb5 100644 --- a/docs/docs/configuration/decimation.md +++ b/docs/docs/configuration/decimation.md @@ -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. diff --git a/src/plugins/plugin.decimation.js b/src/plugins/plugin.decimation.js index abe5e13ef..d0204dc81 100644 --- a/src/plugins/plugin.decimation.js +++ b/src/plugins/plugin.decimation.js @@ -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; diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index 5a7da61e6..9cc8e5624 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -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;