| ---- | ---- | ------- | -----------
| `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.
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;
// 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;
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;