Skip to content

Commit 5106768

Browse files
authored
feat: add ability to hover, select, and filter points upon draw() (#142)
* Add ability to hover, select, and filter points upon `draw()` Also expose `hoveredPoint` via `scatterplot.get('hoveredPoint')` * Document new `draw()` options
1 parent cb202fb commit 5106768

File tree

5 files changed

+83
-6
lines changed

5 files changed

+83
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
## Next
1+
## 1.7.0
22

3+
- Add `preventFilterReset` option to `draw()` to allow re-drawing while keeping the current point filter. [#136](https://github.com/flekschas/regl-scatterplot/pull/136)
4+
- Add ability to hover, select, and filter points immediately when calling `draw(points, { hover: 0, select: [1, 2], filter: [0, 2, 3] })`. Immediately hovering, selecting, or filtering points avoids a filter that can occur when first drawing points and then hovering, selecting, or filtering points subsequently.
35
- Add missing `filteredPoints` type definition. [#139](https://github.com/flekschas/regl-scatterplot/pull/139)
46
- Add missing `selectedPoints` type definition.
57
- Fix drawing a single connecting line between points [#125](https://github.com/flekschas/regl-scatterplot/issues/125)

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ Note that repeatedly calling this method without specifying `points` will not cl
299299
- `transition` [default: `false`]: if `true` and if the current number of points equals `points.length`, the current points will be transitioned to the new points
300300
- `transitionDuration` [default: `500`]: the duration in milliseconds over which the transition should occur
301301
- `transitionEasing` [default: `cubicInOut`]: the easing function, which determines how intermediate values of the transition are calculated
302+
- `preventFilterReset` [default: `false`]: if `true` and if the number of new points is the same as the current number of points, the current point filter will not be reset
303+
- `hover` [default: `undefined`]: a shortcut for [`hover()`](#scatterplot.hover). This option allows to programmatically hover a point by specifying a point index
304+
- `select` [default: `undefined`]: a shortcut for [`select()`](#scatterplot.select). This option allows to programmatically select points by specifying a list of point indices
305+
- `filter` [default: `undefined`]: a shortcut for [`filter()`](#scatterplot.filter). This option allows to programmatically filter points by specifying a list of point indices
302306

303307
**Returns:** a Promise object that resolves once the points have been drawn or transitioned.
304308

@@ -336,12 +340,21 @@ scatterplot.draw([[0.6, 0.6, 0, 0.6]], { transition: true });
336340
// Alternatively, call `scatterplot.clear()`
337341
scatterplot.draw([]);
338342

339-
// Finally, you can also specify the point data in a column-oriented format. The
343+
// You can also specify the point data in a column-oriented format. The
340344
// following call will draw three points: (1,3), (2,2), and (3,1)
341345
scatterplot.draw({
342346
x: [1, 2, 3],
343347
y: [3, 2, 1],
344348
});
349+
350+
// Finally, you can also specify which point will be hovered, which points will
351+
// be selected, and which points will be filtered. These options are useful to
352+
// avoid a flicker which would occur if `hover()`, `select()`, and `filter()`
353+
// are called after `draw()`.
354+
scatterplot.draw(
355+
{ x: [1, 2, 3], y: [3, 2, 1] },
356+
{ hover: 0, selected: [0, 1], filter: [0, 2] }
357+
);
345358
```
346359

347360
<a name="scatterplot.clear" href="#scatterplot.clear">#</a> scatterplot.<b>clear</b>()
@@ -368,7 +381,7 @@ Select some points, such that they get visually highlighted. This will trigger a
368381

369382
**Arguments:**
370383

371-
- `points` is an array of point indices.
384+
- `points` is an array of point indices referencing the points that you want to select.
372385
- `options` [optional] is an object with the following properties:
373386
- `preventEvent`: if `true` the `select` will not be published.
374387

@@ -426,7 +439,7 @@ Programmatically hover a point, such that it gets visually highlighted. This wil
426439

427440
**Arguments:**
428441

429-
- `points` is an array of point indices.
442+
- `point` is the point index referring to the point you want to hover.
430443
- `options` [optional] is an object with the following properties:
431444
- `showReticleOnce`: if `true` the reticle will be shown once, even if `showReticle === false`.
432445
- `preventEvent`: if `true` the `pointover` and `pointout` will not be published.

src/index.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ const createScatterplot = (
463463
let maxValueZ = 0;
464464
let maxValueW = 0;
465465

466+
/** @type{number|undefined} */
466467
let hoveredPoint;
467468
let isMouseInCanvas = false;
468469

@@ -751,7 +752,7 @@ const createScatterplot = (
751752
};
752753

753754
/**
754-
* @param {number | number[]} point
755+
* @param {number} point
755756
* @param {import('./types').ScatterplotMethodOptions['hover']} options
756757
*/
757758
const hover = (
@@ -2068,7 +2069,7 @@ const createScatterplot = (
20682069
select(filteredSelectedPoints, { preventEvent });
20692070

20702071
// Unset any potentially hovered point
2071-
hover(-1, { preventEvent });
2072+
if (!filteredPointsSet.has(hoveredPoint)) hover(-1, { preventEvent });
20722073

20732074
return new Promise((resolve) => {
20742075
const finish = () => {
@@ -2308,6 +2309,18 @@ const createScatterplot = (
23082309

23092310
setPoints(points);
23102311

2312+
if (options.hover !== undefined) {
2313+
hover(options.hover, { preventEvent: true });
2314+
}
2315+
2316+
if (options.select !== undefined) {
2317+
select(options.select, { preventEvent: true });
2318+
}
2319+
2320+
if (options.filter !== undefined) {
2321+
filter(options.filter, { preventEvent: true });
2322+
}
2323+
23112324
if (drawPointConnections) {
23122325
setPointConnections(points).then(() => {
23132326
pubSub.publish('pointConnectionsDraw');
@@ -2907,6 +2920,7 @@ const createScatterplot = (
29072920
if (property === 'opacityInactiveMax') return opacityInactiveMax;
29082921
if (property === 'opacityInactiveScale') return opacityInactiveScale;
29092922
if (property === 'points') return searchIndex.points;
2923+
if (property === 'hoveredPoint') return hoveredPoint;
29102924
if (property === 'selectedPoints') return [...selectedPoints];
29112925
if (property === 'filteredPoints')
29122926
return isPointsFiltered

src/types.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export type Properties = {
148148
isDestroyed: boolean;
149149
isPointsDrawn: boolean;
150150
isPointsFiltered: boolean;
151+
hoveredPoint: number;
151152
filteredPoints: number[];
152153
selectedPoints: number[];
153154
} & Settable;
@@ -159,6 +160,9 @@ export interface ScatterplotMethodOptions {
159160
transitionDuration: number;
160161
transitionEasing: (t: number) => number;
161162
preventFilterReset: boolean;
163+
hover: number;
164+
select: number | number[];
165+
focus: number | number[];
162166
}>;
163167
hover: Partial<{
164168
showReticleOnce: boolean;

tests/index.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,6 +2414,50 @@ test(
24142414
})
24152415
);
24162416

2417+
test(
2418+
'test hover, select, and filter options of `draw()`',
2419+
catchError(async (t) => {
2420+
const scatterplot = createScatterplot({
2421+
canvas: createCanvas(200, 200),
2422+
width: 200,
2423+
height: 200,
2424+
});
2425+
2426+
const points = [
2427+
[0, 0],
2428+
[1, 1],
2429+
[1, -1],
2430+
[-1, -1],
2431+
[-1, 1],
2432+
];
2433+
2434+
await scatterplot.draw(points, {
2435+
hover: 0,
2436+
select: [1, 2],
2437+
filter: [0, 2, 3],
2438+
});
2439+
2440+
await wait(50);
2441+
2442+
t.equal(
2443+
scatterplot.get('hoveredPoint'),
2444+
0,
2445+
'should be hovering the first point'
2446+
);
2447+
2448+
t.equal(
2449+
scatterplot.get('selectedPoints'),
2450+
[2],
2451+
'should have selected the third point'
2452+
);
2453+
2454+
t.ok(
2455+
isSameElements(scatterplot.get('filteredPoints'), [0, 2, 3]),
2456+
'should have filtered down to points 0, 2, and 3'
2457+
);
2458+
})
2459+
);
2460+
24172461
test(
24182462
'zooming with transition',
24192463
catchError(async (t) => {

0 commit comments

Comments
 (0)