Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a new hook to enable data decimation (#8255)
* Create a new hook to enable data decimation The `beforeElementUpdate` hook can be used to decimate data. The chart elements will not be created until after this hook has fired ensuring that if decimation occurs, only the needed elements will be created. * Address code review feedback * Rename hook to beforeElementsUpdate * Simplify parsing logic * Add decimation plugin to the core * Allow a dataset to specify a different data key * Decimation plugin uses the dataKey feature * Refactor the decimation plugin to support configurable algorithms * Lint the plugin changes * Tests for the dataKey feature * Convert test files to tabs * Standardize on tabs in ts files * Remove the dataKey feature * Replace dataKey usage in decimation plugin We define a new descriptor for the `data` key allowing the plugin to be simpler. * Disable decimation when indexAxis is Y * Simplify the decimation width approximation * Resolve the indexAxis correctly in all cases * Initial documentation * Reverse check * Update TS definitions for new plugin options * Move defineProperty after bailouts * Add destroy hook
- Loading branch information
Showing
8 changed files
with
213 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
--- | ||
title: Data Decimation | ||
--- | ||
|
||
The decimation plugin can be used with line charts to automatically decimate data at the start of the chart lifecycle. Before enabling this plugin, review the [requirements](#requirements) to ensure that it will work with the chart you want to create. | ||
|
||
## Configuration Options | ||
|
||
The decimation plugin configuration is passed into the `options.plugins.decimation` namespace. The global options for the plugin are defined in `Chart.defaults.plugins.decimation`. | ||
|
||
| Name | Type | Default | Description | ||
| ---- | ---- | ------- | ----------- | ||
| `enabled` | `boolean` | `true` | Is decimation enabled? | ||
| `algorithm` | `string` | `'min-max'` | Decimation algorithm to use. See the [more...](#decimation-algorithms) | ||
|
||
## Decimation Algorithms | ||
|
||
Decimation algorithm to use for data. Options are: | ||
|
||
* `'min-max'` | ||
|
||
### 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. | ||
|
||
## Requirements | ||
|
||
To use the decimation plugin, the following requirements must be met: | ||
|
||
1. The dataset must have an `indexAxis` of `'x'` | ||
2. The dataset must be a line | ||
3. The X axis for the dataset must be either a `'linear'` or `'time'` type axis | ||
4. The dataset object must be mutable. The plugin stores the original data as `dataset._data` and then defines a new `data` property on the dataset. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import {isNullOrUndef, resolve} from '../helpers'; | ||
|
||
function minMaxDecimation(data, availableWidth) { | ||
let i, point, x, y, prevX, minIndex, maxIndex, minY, maxY; | ||
const decimated = []; | ||
|
||
const xMin = data[0].x; | ||
const xMax = data[data.length - 1].x; | ||
const dx = xMax - xMin; | ||
|
||
for (i = 0; i < data.length; ++i) { | ||
point = data[i]; | ||
x = (point.x - xMin) / dx * availableWidth; | ||
y = point.y; | ||
const truncX = x | 0; | ||
|
||
if (truncX === prevX) { | ||
// Determine `minY` / `maxY` and `avgX` while we stay within same x-position | ||
if (y < minY) { | ||
minY = y; | ||
minIndex = i; | ||
} else if (y > maxY) { | ||
maxY = y; | ||
maxIndex = i; | ||
} | ||
} else { | ||
// Push up to 4 points, 3 for the last interval and the first point for this interval | ||
if (minIndex && maxIndex) { | ||
decimated.push(data[minIndex], data[maxIndex]); | ||
} | ||
if (i > 0) { | ||
// Last point in the previous interval | ||
decimated.push(data[i - 1]); | ||
} | ||
decimated.push(point); | ||
prevX = truncX; | ||
minY = maxY = y; | ||
minIndex = maxIndex = i; | ||
} | ||
} | ||
|
||
return decimated; | ||
} | ||
|
||
export default { | ||
id: 'decimation', | ||
|
||
defaults: { | ||
algorithm: 'min-max', | ||
enabled: false, | ||
}, | ||
|
||
beforeElementsUpdate: (chart, args, options) => { | ||
if (!options.enabled) { | ||
return; | ||
} | ||
|
||
// Assume the entire chart is available to show a few more points than needed | ||
const availableWidth = chart.width; | ||
|
||
chart.data.datasets.forEach((dataset, datasetIndex) => { | ||
const {_data, indexAxis} = dataset; | ||
const meta = chart.getDatasetMeta(datasetIndex); | ||
const data = _data || dataset.data; | ||
|
||
if (resolve([indexAxis, chart.options.indexAxis]) === 'y') { | ||
// Decimation is only supported for lines that have an X indexAxis | ||
return; | ||
} | ||
|
||
if (meta.type !== 'line') { | ||
// Only line datasets are supported | ||
return; | ||
} | ||
|
||
const xAxis = chart.scales[meta.xAxisID]; | ||
if (xAxis.type !== 'linear' && xAxis.type !== 'time') { | ||
// Only linear interpolation is supported | ||
return; | ||
} | ||
|
||
if (chart.options.parsing) { | ||
// Plugin only supports data that does not need parsing | ||
return; | ||
} | ||
|
||
if (data.length <= 4 * availableWidth) { | ||
// No decimation is required until we are above this threshold | ||
return; | ||
} | ||
|
||
if (isNullOrUndef(_data)) { | ||
// First time we are seeing this dataset | ||
// We override the 'data' property with a setter that stores the | ||
// raw data in _data, but reads the decimated data from _decimated | ||
// TODO: Undo this on chart destruction | ||
dataset._data = data; | ||
delete dataset.data; | ||
Object.defineProperty(dataset, 'data', { | ||
configurable: true, | ||
enumerable: true, | ||
get: function() { | ||
return this._decimated; | ||
}, | ||
set: function(d) { | ||
this._data = d; | ||
} | ||
}); | ||
} | ||
|
||
// Point the chart to the decimated data | ||
let decimated; | ||
switch (options.algorithm) { | ||
case 'min-max': | ||
decimated = minMaxDecimation(data, availableWidth); | ||
break; | ||
default: | ||
throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`); | ||
} | ||
|
||
dataset._decimated = decimated; | ||
}); | ||
}, | ||
|
||
destroy(chart) { | ||
chart.data.datasets.forEach((dataset) => { | ||
if (dataset._decimated) { | ||
const data = dataset._data; | ||
delete dataset._decimated; | ||
delete dataset._data; | ||
Object.defineProperty(dataset, 'data', {value: data}); | ||
} | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters