Skip to content

Commit 532f9fd

Browse files
committed
Internal lib - Add LTTB algorithm
1 parent dbdf9fb commit 532f9fd

File tree

2 files changed

+1418
-1083
lines changed

2 files changed

+1418
-1083
lines changed

src/lib.js

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1942,7 +1942,7 @@ export function createPolarAreas({ series, center, maxRadius }) {
19421942
middlePoint: { x: middleX, y: middleY }
19431943
};
19441944
});
1945-
1945+
19461946
return paths;
19471947
}
19481948

@@ -1963,6 +1963,137 @@ export function createShadesOfGrey(startColor, steps) {
19631963
return shades;
19641964
}
19651965

1966+
/**
1967+
* LTTB algorithm for a ds with coordinates
1968+
* @param {Array<Object>} data
1969+
* @param {number} threshold
1970+
* @returns {Array<Object>}
1971+
*/
1972+
export function largestTriangleThreeBuckets({ data, threshold }) {
1973+
if (threshold >= data.length || threshold < 3) {
1974+
return data;
1975+
}
1976+
const sampled = [];
1977+
const bucketSize = (data.length - 2) / (threshold - 2);
1978+
let a = 0;
1979+
// First point as is
1980+
sampled.push(data[a]);
1981+
for (let i = 0; i < threshold - 2; i += 1) {
1982+
const bucketStart = Math.floor((i + 1) * bucketSize) + 1;
1983+
const bucketEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, data.length);
1984+
const bucket = data.slice(bucketStart, bucketEnd);
1985+
let averageX = 0;
1986+
let averageY = 0;
1987+
for (const point of bucket) {
1988+
averageX += point.x;
1989+
averageY += point.y;
1990+
}
1991+
averageX /= bucket.length;
1992+
averageY /= bucket.length;
1993+
let maxArea = -1;
1994+
let nextA = a;
1995+
for (let j = bucketStart; j < bucketEnd; j += 1) {
1996+
const area = Math.abs(
1997+
(data[a].x - averageX) * (data[j].y - data[a].y) -
1998+
(data[a].x - data[j].x) * (averageY - data[a].y)
1999+
);
2000+
if (area > maxArea) {
2001+
maxArea = area;
2002+
nextA = j;
2003+
}
2004+
}
2005+
sampled.push(data[nextA]);
2006+
a = nextA;
2007+
}
2008+
// Last point as is
2009+
sampled.push(data[data.length - 1]);
2010+
return sampled;
2011+
}
2012+
2013+
/**
2014+
* LTTB algorithm for an array of numbers.
2015+
* @param {Array<number>} data
2016+
* @param {number} threshold
2017+
* @returns {Array<number>}
2018+
*/
2019+
export function largestTriangleThreeBucketsArray({ data, threshold }) {
2020+
if (threshold >= data.length || threshold < 3) {
2021+
return data;
2022+
}
2023+
const sampled = [];
2024+
const bucketSize = (data.length - 2) / (threshold - 2);
2025+
let a = 0;
2026+
// First point as is
2027+
sampled.push(data[a]);
2028+
for (let i = 0; i < threshold - 2; i += 1) {
2029+
const bucketStart = Math.floor((i + 1) * bucketSize) + 1;
2030+
const bucketEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, data.length);
2031+
const bucket = data.slice(bucketStart, bucketEnd);
2032+
const average = bucket.reduce((a, b) => a + b, 0) / bucket.length;
2033+
let maxArea = -1;
2034+
let nextA = a;
2035+
2036+
for (let j = bucketStart; j < bucketEnd; j += 1) {
2037+
const area = Math.abs((data[a] - average) * (j - a));
2038+
if (area > maxArea) {
2039+
maxArea = area;
2040+
nextA = j;
2041+
}
2042+
}
2043+
sampled.push(data[nextA]);
2044+
a = nextA;
2045+
}
2046+
// Last point as is
2047+
sampled.push(data[data.length - 1]);
2048+
return sampled;
2049+
}
2050+
2051+
/**
2052+
* LTTB algorithm for an array of objects containing 'name' and 'value'.
2053+
* @param {Array<{ name: string, value: number }>} data
2054+
* @param {number} threshold
2055+
* @returns {Array<{ name: string, value: number }>}
2056+
*/
2057+
export function largestTriangleThreeBucketsArrayObjects({ data, threshold, key='value' }) {
2058+
if (threshold >= data.length || threshold < 3) {
2059+
return data;
2060+
}
2061+
2062+
const sampled = [];
2063+
const bucketSize = (data.length - 2) / (threshold - 2);
2064+
let a = 0;
2065+
2066+
// First point as is
2067+
sampled.push(data[a]);
2068+
2069+
for (let i = 0; i < threshold - 2; i += 1) {
2070+
const bucketStart = Math.floor((i + 1) * bucketSize) + 1;
2071+
const bucketEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, data.length);
2072+
const bucket = data.slice(bucketStart, bucketEnd);
2073+
2074+
const average = bucket.reduce((sum, point) => sum + point[key], 0) / bucket.length;
2075+
2076+
let maxArea = -1;
2077+
let nextA = a;
2078+
2079+
for (let j = bucketStart; j < bucketEnd; j += 1) {
2080+
const area = Math.abs((data[a][key] - average) * (j - a));
2081+
if (area > maxArea) {
2082+
maxArea = area;
2083+
nextA = j;
2084+
}
2085+
}
2086+
2087+
sampled.push(data[nextA]);
2088+
a = nextA;
2089+
}
2090+
2091+
sampled.push(data[data.length - 1]);
2092+
2093+
return sampled;
2094+
}
2095+
2096+
19662097
const lib = {
19672098
XMLNS,
19682099
abbreviate,
@@ -2016,6 +2147,8 @@ const lib = {
20162147
isFunction,
20172148
isSafeValue,
20182149
isValidUserValue,
2150+
largestTriangleThreeBucketsArray,
2151+
largestTriangleThreeBucketsArrayObjects,
20192152
lightenHexColor,
20202153
makeDonut,
20212154
makePath,

0 commit comments

Comments
 (0)