Documentation / @finchart/core / index / M4Decimation
Class: M4Decimation<T>
Defined in: packages/core/src/data/decimation.ts:251
Picks four per pixel column: first, last, min, max.
The default strategy. A strategy that keeps only one point per bucket (grid, LttbDecimation) always loses one when a bucket has both an up-spike and a down-spike. M4 picks four, so it never loses that — the original paper proves that these four per column produce a line that's pixel-identical to the original (Jugel et al., VLDB 2014).
So it makes a quarter as many buckets as threshold. In a chart of width w, for one column to be one bucket, threshold has to be 4w — that's why pointsPerPixel defaults to 4.
Buckets split by screen place, not index. A column is a place on screen, not a place in the array. Splitting by index on data clustered to one side crushes the sparse side into a single bucket. Screen place is usually x, but in a bar-index coordinate space, screenXScan recovers it.
Type Parameters
T
T extends BaseDataPoint = BaseDataPoint
Implements
Constructors
Constructor
new M4Decimation<
T>(coordinates?):M4Decimation<T>
Defined in: packages/core/src/data/decimation.ts:259
Defaults to the same {x, y} accessor as its siblings — without it, an untyped consumer's new M4Decimation() would blow up on the first decimate with a raw TypeError leaking an internal name.
Parameters
coordinates?
CoordinateAccessor<T> = ...
Returns
M4Decimation<T>
Methods
decimate()
decimate(
data,range,threshold,screenXScan?,gapFree?):T[]
Defined in: packages/core/src/data/decimation.ts:263
Decimates only the [range.start, range.end) window, not all of data.
Why the window is a range, not an array: if the caller copied the visible range with slice, that's a big new array every frame on large data, most of it thrown away unused. Why the range is required, not optional: if it were optional, a strategy that doesn't know the range could decimate the whole array and the type system wouldn't catch it.
screenXScan is a factory that opens a pass for screen-place lookups (see Viewport.screenXScan). A strategy that buckets by x (M4Decimation, LttbDecimation) has to bucket in that same space when the screen space differs. Each sweep opens one pass — the cursor belongs to that pass, so strategies and frames can't interfere with each other. If absent, x is the screen place (continuous coordinate space, the default).
gapFree is a fact the caller already knows — if true, this data is guaranteed gap-free, so the window scan that would otherwise find gap boundaries is skipped. Gap presence is a property of the array, not the window, so the viewport cache can't save this for you — only the side that owns the changes can maintain it incrementally.
Not merged with gapless — that's a static fact about the accessor, this is runtime state of the array.
False covers "don't know," not just "has gaps" — the safe default is false. Passing true incorrectly swallows gaps, and the line runs straight across a spot that should have no value.
Parameters
data
T[]
range
threshold
number
screenXScan?
() => (x) => number
gapFree?
boolean
Returns
T[]