Skip to content

Commit c408f19

Browse files
committed
Improvement - Use LTTB algorithm
1 parent 2634864 commit c408f19

File tree

6 files changed

+89
-32
lines changed

6 files changed

+89
-32
lines changed

src/components/vue-ui-quadrant.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
functionReturnsString,
1414
giftWrap,
1515
isFunction,
16+
largestTriangleThreeBuckets,
1617
objectIsEmpty,
1718
palette,
1819
setOpacity,
@@ -445,6 +446,10 @@ const segregated = ref([]);
445446
const immutableDataset = computed(() => props.dataset.map((category, i) => {
446447
return {
447448
...category,
449+
series: largestTriangleThreeBuckets({
450+
data: category.series,
451+
threshold: FINAL_CONFIG.value.downsample.threshold
452+
}),
448453
id: `cat_${i}_${uid.value}`,
449454
color: convertColorToHex(category.color) || customPalette.value[i] || palette[i],
450455
}
@@ -1325,7 +1330,7 @@ defineExpose({
13251330
/>
13261331
</g>
13271332

1328-
<g v-if="mutableConfig.plotLabels.show">
1333+
<g v-if="mutableConfig.plotLabels.show" style="pointer-events: none;">
13291334
<g v-for="category in drawableDataset">
13301335
<text
13311336
v-for="plot in category.series"

src/components/vue-ui-scatter.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getMissingDatasetAttributes,
1616
giftWrap,
1717
isFunction,
18+
largestTriangleThreeBuckets,
1819
objectIsEmpty,
1920
palette,
2021
setOpacity,
@@ -234,6 +235,10 @@ const datasetWithId = computed(() => {
234235
const id = `cluster_${uid.value}_${i}`;
235236
return {
236237
...ds,
238+
values: largestTriangleThreeBuckets({
239+
data: ds.values,
240+
threshold: FINAL_CONFIG.value.downsample.threshold
241+
}),
237242
id,
238243
color: ds.color ? ds.color : (customPalette.value[i] || palette[i] || palette[i % palette.length]),
239244
opacity: segregated.value.includes(id) ? 0.5: 1,

src/components/vue-ui-spark-trend.vue

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
createUid,
99
dataLabel,
1010
error,
11+
largestTriangleThreeBucketsArray,
1112
objectIsEmpty,
1213
setOpacity,
1314
XMLNS,
@@ -68,13 +69,21 @@ function prepareConfig() {
6869
}
6970
}
7071
72+
const downsampled = computed(() => largestTriangleThreeBucketsArray({
73+
data: props.dataset,
74+
threshold: FINAL_CONFIG.value.downsample.threshold
75+
}))
76+
7177
watch(() => props.config, (_newCfg) => {
7278
FINAL_CONFIG.value = prepareConfig();
7379
prepareChart();
7480
}, { deep: true });
7581
7682
watch(() => props.dataset, (_) => {
77-
safeDatasetCopy.value = props.dataset.map(v => {
83+
safeDatasetCopy.value = largestTriangleThreeBucketsArray({
84+
data: props.dataset,
85+
threshold: FINAL_CONFIG.value.downsample.threshold
86+
}).map(v => {
7887
return ![undefined, Infinity, -Infinity, null, NaN].includes(v) ? v : null
7988
})
8089
}, { deep: true })
@@ -83,7 +92,10 @@ function sanitize(arr) {
8392
return arr.map(v => checkNaN(v))
8493
}
8594
86-
const safeDatasetCopy = ref(props.dataset.map(v => {
95+
const safeDatasetCopy = ref(largestTriangleThreeBucketsArray({
96+
data: props.dataset,
97+
threshold: FINAL_CONFIG.value.downsample.threshold
98+
}).map(v => {
8799
if(FINAL_CONFIG.value.style.animation.show) {
88100
return null
89101
} else {
@@ -111,13 +123,13 @@ onMounted(() => {
111123
let elapsed = now - then;
112124
if (elapsed > interval) {
113125
then = now - (elapsed % interval);
114-
if (start < props.dataset.length) {
115-
safeDatasetCopy.value.push(props.dataset[start]);
126+
if (start < downsampled.value.length) {
127+
safeDatasetCopy.value.push(downsampled.value[start]);
116128
start += 1;
117129
raf.value = requestAnimationFrame(animate);
118130
} else {
119131
cancelAnimationFrame(raf.value)
120-
safeDatasetCopy.value = sanitize(props.dataset);
132+
safeDatasetCopy.value = sanitize(downsampled.value);
121133
isAnimating.value = false;
122134
}
123135
} else {
@@ -154,7 +166,7 @@ const drawingArea = computed(() => {
154166
});
155167
156168
const extremes = computed(() => {
157-
const ds = sanitize(props.dataset);
169+
const ds = sanitize(downsampled.value);
158170
return {
159171
max: Math.max(...ds.map(v => checkNaN(v))),
160172
min: Math.min(...ds.map(v => checkNaN(v)))
@@ -174,7 +186,7 @@ function ratioToMax(v) {
174186
return v / absoluteMax.value;
175187
}
176188
177-
const len = computed(() => props.dataset.length);
189+
const len = computed(() => downsampled.value.length);
178190
179191
const mutableDataset = computed(() => {
180192
return safeDatasetCopy.value.map((v, i) => {
@@ -191,7 +203,7 @@ const mutableDataset = computed(() => {
191203
});
192204
193205
const trendValue = computed(() => {
194-
const ds = sanitize(props.dataset);
206+
const ds = sanitize(downsampled.value);
195207
if (FINAL_CONFIG.value.style.trendLabel.trendType === 'global') {
196208
return calcTrend(ds)
197209
}

src/components/vue-ui-sparkline.vue

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
dataLabel as dl,
1010
error,
1111
getMissingDatasetAttributes,
12+
largestTriangleThreeBucketsArrayObjects,
1213
objectIsEmpty,
1314
setOpacity,
1415
shiftHue,
@@ -82,36 +83,49 @@ function prepareConfig() {
8283
}
8384
}
8485
86+
const downsampled = computed(() => {
87+
return largestTriangleThreeBucketsArrayObjects({
88+
data: props.dataset,
89+
threshold: FINAL_CONFIG.value.downsample.threshold
90+
})
91+
})
92+
8593
watch(() => props.config, (_newCfg) => {
8694
FINAL_CONFIG.value = prepareConfig();
8795
prepareChart();
8896
svg.value.chartWidth = FINAL_CONFIG.value.style.chartWidth;
8997
}, { deep: true });
9098
9199
watch(() => props.dataset, (_) => {
92-
safeDatasetCopy.value = props.dataset.map(d => {
100+
safeDatasetCopy.value = largestTriangleThreeBucketsArrayObjects({
101+
data: props.dataset.map(d => {
93102
return {
94103
...d,
95104
value: ![undefined].includes(d.value) ? d.value : null
96105
}
106+
}),
107+
threshold: FINAL_CONFIG.value.downsample.threshold
97108
})
98109
}, { deep: true })
99110
100111
const safeDatasetCopy = ref(prepareDsCopy());
101112
102113
function prepareDsCopy() {
103-
return props.dataset.map(d => {
104-
if (FINAL_CONFIG.value.style.animation.show) {
105-
return {
106-
...d,
107-
value: null
108-
}
109-
} else {
110-
return {
111-
...d,
112-
value: ![undefined].includes(d.value) ? d.value : null
114+
return largestTriangleThreeBucketsArrayObjects({
115+
data: props.dataset.map(d => {
116+
if (FINAL_CONFIG.value.style.animation.show) {
117+
return {
118+
...d,
119+
value: null
120+
}
121+
} else {
122+
return {
123+
...d,
124+
value: ![undefined].includes(d.value) ? d.value : null
125+
}
113126
}
114-
}
127+
}),
128+
threshold: FINAL_CONFIG.value.downsample.threshold
115129
})
116130
}
117131
@@ -125,17 +139,17 @@ onMounted(() => {
125139
let start = 0;
126140
127141
function animate() {
128-
if (start < props.dataset.length) {
129-
safeDatasetCopy.value.push(props.dataset[start])
142+
if (start < downsampled.value.length) {
143+
safeDatasetCopy.value.push(downsampled.value[start])
130144
setTimeout(() => {
131145
requestAnimationFrame(animate)
132146
}, chunks)
133147
} else {
134-
safeDatasetCopy.value = props.dataset
148+
safeDatasetCopy.value = downsampled.value
135149
}
136150
start += 1;
137151
}
138-
animate()
152+
animate();
139153
}
140154
})
141155
@@ -228,7 +242,7 @@ function ratioToMax(v) {
228242
return v / absoluteMax.value;
229243
}
230244
231-
const len = computed(() => props.dataset.length - 1);
245+
const len = computed(() => downsampled.value.length - 1);
232246
233247
const mutableDataset = computed(() => {
234248
return safeDatasetCopy.value.map((s, i) => {

src/components/vue-ui-xy-canvas.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
functionReturnsString,
2121
isFunction,
2222
lightenHexColor,
23+
largestTriangleThreeBucketsArray,
2324
objectIsEmpty,
2425
palette,
2526
sanitizeArray,
@@ -267,7 +268,10 @@ const dsCopy = computed(() => {
267268
return props.dataset.map((ds, i) => {
268269
return {
269270
...ds,
270-
series: sanitizeArray(ds.series),
271+
series: largestTriangleThreeBucketsArray({
272+
data: sanitizeArray(ds.series),
273+
threshold: FINAL_CONFIG.value.downsample.threshold
274+
}),
271275
absoluteIndex: i,
272276
color: convertColorToHex(ds.color || customPalette.value[i] || palette[i] || palette[i % palette.length]),
273277
}

src/components/vue-ui-xy.vue

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,7 +1201,8 @@ import {
12011201
functionReturnsString,
12021202
hasDeepProperty,
12031203
isFunction,
1204-
isSafeValue,
1204+
isSafeValue,
1205+
largestTriangleThreeBucketsArray,
12051206
opacity,
12061207
palette,
12071208
setOpacity,
@@ -1272,7 +1273,10 @@ export default {
12721273
})
12731274
}
12741275
})
1275-
const maxX = Math.max(...this.dataset.map(datapoint => datapoint.series.length));
1276+
1277+
const lttbThreshold = this.config.downsample ? this.config.downsample.threshold ? this.config.downsample.threshold : 500 : 500
1278+
1279+
const maxX = Math.max(...this.dataset.map(datapoint => this.largestTriangleThreeBucketsArray({data: datapoint.series, threshold: lttbThreshold}).length));
12761280
const slicer = {
12771281
start: 0,
12781282
end: maxX,
@@ -1347,7 +1351,10 @@ export default {
13471351
watch: {
13481352
dataset: {
13491353
handler(_newDs, _oldDs) {
1350-
this.maxX = Math.max(...this.dataset.map(datapoint => datapoint.series.length));
1354+
this.maxX = Math.max(...this.dataset.map(datapoint => this.largestTriangleThreeBucketsArray({
1355+
data: datapoint.series,
1356+
threshold: this.FINAL_CONFIG.downsample.threshold
1357+
}).length));
13511358
this.slicer = {
13521359
start: 0,
13531360
end: this.maxX
@@ -1504,16 +1511,25 @@ export default {
15041511
return this.dataset.map((datapoint, i) => {
15051512
return {
15061513
...datapoint,
1514+
series: this.largestTriangleThreeBucketsArray({
1515+
data: datapoint.series,
1516+
threshold: this.FINAL_CONFIG.downsample.threshold
1517+
}),
15071518
id: `uniqueId_${i}`
15081519
}
15091520
});
15101521
},
15111522
safeDataset(){
15121523
if(!this.useSafeValues) return this.dataset;
1524+
15131525
return this.dataset.map((datapoint, i) => {
1526+
const LTTD = this.largestTriangleThreeBucketsArray({
1527+
data: datapoint.series,
1528+
threshold: this.FINAL_CONFIG.downsample.threshold
1529+
})
15141530
return {
15151531
...datapoint,
1516-
series: datapoint.series.map(d => {
1532+
series: LTTD.map(d => {
15171533
return this.isSafeValue(d) ? d : null
15181534
}).slice(this.slicer.start, this.slicer.end),
15191535
color: this.convertColorToHex(datapoint.color ? datapoint.color : this.customPalette[i] ? this.customPalette[i] : this.palette[i]),
@@ -2252,6 +2268,7 @@ export default {
22522268
hasDeepProperty,
22532269
isFunction,
22542270
isSafeValue,
2271+
largestTriangleThreeBucketsArray,
22552272
objectIsEmpty,
22562273
setOpacity,
22572274
shiftHue,
@@ -2549,7 +2566,7 @@ export default {
25492566
refreshSlicer() {
25502567
this.slicer = {
25512568
start: 0,
2552-
end: Math.max(...this.dataset.map(datapoint => datapoint.series.length))
2569+
end: Math.max(...this.dataset.map(datapoint => this.largestTriangleThreeBucketsArray({data:datapoint.series, threshold: this.FINAL_CONFIG.downsample.threshold}).length))
25532570
}
25542571
this.slicerStep += 1;
25552572
},

0 commit comments

Comments
 (0)