Skip to content

Commit 2bb3704

Browse files
authored
fix: filter stacking bug (#199)
* fix: enforce consistent point ordering w/ `filter()` * chore: bump biome schema version * docs: update changelog
1 parent 90059b5 commit 2bb3704

5 files changed

Lines changed: 107 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.11.1
2+
3+
- Fix: ensure that the drawing order of points cannot be manipulated via `scatterplot.filter()` ([#197](https://github.com/flekschas/regl-scatterplot/issues/197))
4+
15
## 1.11.0
26

37
- Feat: add support for linear and constant point scaling via a new property called `pointScaleMode`.

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
33
"organizeImports": {
44
"enabled": true
55
},

src/index.js

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ import {
131131
dist,
132132
flipObj,
133133
getBBox,
134+
insertionSort,
134135
isConditionalArray,
135136
isDomRect,
136137
isHorizontalLine,
@@ -2218,34 +2219,37 @@ const createScatterplot = (
22182219
* @param {import('./types').ScatterplotMethodOptions['filter']}
22192220
*/
22202221
const filter = (pointIdxs, { preventEvent = false } = {}) => {
2221-
const filteredPoints = Array.isArray(pointIdxs) ? pointIdxs : [pointIdxs];
2222-
22232222
isPointsFiltered = true;
22242223
filteredPointsSet.clear();
22252224

2225+
const pointIdxsArray = Array.isArray(pointIdxs) ? pointIdxs : [pointIdxs];
2226+
const filteredPoints = [];
22262227
const filteredPointsBuffer = [];
22272228
const filteredSelectedPoints = [];
22282229

2229-
for (let i = filteredPoints.length - 1; i >= 0; i--) {
2230-
const pointIdx = filteredPoints[i];
2231-
2230+
for (const pointIdx of pointIdxsArray) {
22322231
if (pointIdx < 0 || pointIdx >= numPoints) {
2233-
// Remove invalid filtered points
2234-
filteredPoints.splice(i, 1);
2232+
// Skip invalid filtered points
22352233
continue;
22362234
}
22372235

2236+
filteredPoints.push(pointIdx);
22382237
filteredPointsSet.add(pointIdx);
2239-
filteredPointsBuffer.push.apply(
2240-
filteredPointsBuffer,
2241-
indexToStateTexCoord(pointIdx),
2242-
);
22432238

22442239
if (selectedPointsSet.has(pointIdx)) {
22452240
filteredSelectedPoints.push(pointIdx);
22462241
}
22472242
}
22482243

2244+
const sortedFilteredPoints = insertionSort([...filteredPoints]);
2245+
2246+
for (const pointIdx of sortedFilteredPoints) {
2247+
filteredPointsBuffer.push.apply(
2248+
filteredPointsBuffer,
2249+
indexToStateTexCoord(pointIdx),
2250+
);
2251+
}
2252+
22492253
// Update the normal points index buffers
22502254
normalPointsIndexBuffer.subdata(filteredPointsBuffer);
22512255

src/utils.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,19 @@ export const isRect = (annotation) =>
526526

527527
export const isPolygon = (annotation) =>
528528
'vertices' in annotation && annotation.vertices.length > 1;
529+
530+
export const insertionSort = (array) => {
531+
const end = array.length;
532+
for (let i = 1; i < end; i++) {
533+
// Choosing the first element in our unsorted subarray
534+
const current = array[i];
535+
// The last element of our sorted subarray
536+
let j = i - 1;
537+
while (j > -1 && current < array[j]) {
538+
array[j + 1] = array[j];
539+
j--;
540+
}
541+
array[j + 1] = current;
542+
}
543+
return array;
544+
};

tests/index.js

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2802,18 +2802,88 @@ test('regl-scatterplot', async (t2) => {
28022802
'should have not filter down to any point because -1 is an invalid point index'
28032803
);
28042804

2805-
await scatterplot.filter([0, -1, 2, 4, 6]);
2805+
const pointsToBeFiltered = [0, -1, 2, 4, 6];
2806+
2807+
await scatterplot.filter(pointsToBeFiltered);
28062808
await wait(0);
28072809

28082810
t.ok(
28092811
isSameElements(filteredPoints, [0, 2, 4]),
28102812
'should have filtered down to valid points (0, 2, and 4) only'
28112813
);
28122814

2815+
// We're testing this due to the following bug where we accidentically
2816+
// spliced of from the input argument, which we should never do.
2817+
// @see https://github.com/flekschas/regl-scatterplot/issues/197
2818+
t.equal(
2819+
pointsToBeFiltered.length,
2820+
5,
2821+
'should have not altered `pointsToBeFiltered`'
2822+
);
2823+
28132824
scatterplot.destroy();
28142825
})
28152826
);
28162827

2828+
await t2.test(
2829+
'`filter() point order consistency`',
2830+
catchError(async (t) => {
2831+
const scatterplot = createScatterplot({
2832+
canvas: createCanvas(50, 50),
2833+
width: 50,
2834+
height: 50,
2835+
pointColor: ['#FF0000', '#00FF00', '#0000FF'],
2836+
pointSize: 50,
2837+
opacity: 1,
2838+
colorBy: "valueA"
2839+
});
2840+
2841+
const dpr = window.devicePixelRatio;
2842+
const middlePixel = (50 * dpr * 25 * dpr + 25 * dpr) * 4;
2843+
2844+
await scatterplot.draw({
2845+
x: [0, 0, 0],
2846+
y: [0, 0, 0],
2847+
valueA: [0, 1, 2]
2848+
});
2849+
2850+
await wait(25);
2851+
2852+
let image = scatterplot.export();
2853+
2854+
t.equal(
2855+
Array.from(image.data.slice(middlePixel, middlePixel + 4)),
2856+
[0, 0, 255, 255],
2857+
'the center pixel should be blue'
2858+
);
2859+
2860+
await scatterplot.filter([1, 0]);
2861+
2862+
await wait(25);
2863+
2864+
image = scatterplot.export();
2865+
2866+
t.equal(
2867+
Array.from(image.data.slice(middlePixel, middlePixel + 4)),
2868+
[0, 255, 0, 255],
2869+
'the center pixel should be green'
2870+
);
2871+
2872+
await scatterplot.unfilter();
2873+
await scatterplot.filter([0, 1]);
2874+
2875+
await wait(25);
2876+
2877+
image = scatterplot.export();
2878+
2879+
t.equal(
2880+
Array.from(image.data.slice(middlePixel, middlePixel + 4)),
2881+
[0, 255, 0, 255],
2882+
'the center pixel should be green'
2883+
);
2884+
})
2885+
);
2886+
28172887
await t2.test(
28182888
'test hover, select, and filter options of `draw()`',
28192889
catchError(async (t) => {

0 commit comments

Comments
 (0)