Skip to content

Commit ddf4dd8

Browse files
authored
fix: handle camera fixing better (#202)
1 parent bfc7b4b commit ddf4dd8

7 files changed

Lines changed: 91 additions & 5 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.2
2+
3+
- Fix: handle camera fixing better by adding a dedicated prop called `cameraIsFixed`. Previously, the lasso end interaction would unset the camera fixing. ([#94](https://github.com/flekschas/regl-scatterplot/issues/94))
4+
15
## 1.11.1
26

37
- Fix: ensure that the drawing order of points cannot be manipulated via `scatterplot.filter()` ([#197](https://github.com/flekschas/regl-scatterplot/issues/197))

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,8 @@ can be read and written via [`scatterplot.get()`](#scatterplot.get) and [`scatte
756756
| cameraTarget | tuple | `[0, 0]` | | `true` | `false` |
757757
| cameraDistance | float | `1` | > 0 | `true` | `false` |
758758
| cameraRotation | float | `0` | | `true` | `false` |
759-
| cameraView | Float32Array | `[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1`] | | `true` | `false` |
759+
| cameraView | Float32Array | `[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]` | | `true` | `false` |
760+
| cameraIsFixed | boolean | `false` | | `true` | `false` |
760761
| colorBy | string | `null` | See [data encoding](#property-by) | `true` | `true` |
761762
| sizeBy | string | `null` | See [data encoding](#property-by) | `true` | `true` |
762763
| opacityBy | string | `null` | See [data encoding](#property-by) | `true` | `true` |

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export const Z_NAMES = new Set(['z', 'valueZ', 'valueA', 'value1', 'category']);
166166
export const W_NAMES = new Set(['w', 'valueW', 'valueB', 'value2', 'value']);
167167
export const DEFAULT_IMAGE_LOAD_TIMEOUT = 15000;
168168
export const DEFAULT_SPATIAL_INDEX_USE_WORKER = undefined;
169+
export const DEFAULT_CAMERA_IS_FIXED = false;
169170

170171
// Error messages
171172
export const ERROR_POINTS_NOT_DRAWN = 'Points have not been drawn';

src/index.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
DEFAULT_ANNOTATION_LINE_COLOR,
3939
DEFAULT_ANNOTATION_LINE_WIDTH,
4040
DEFAULT_BACKGROUND_IMAGE,
41+
DEFAULT_CAMERA_IS_FIXED,
4142
DEFAULT_COLOR_ACTIVE,
4243
DEFAULT_COLOR_BG,
4344
DEFAULT_COLOR_BY,
@@ -287,6 +288,7 @@ const createScatterplot = (
287288
annotationLineColor = DEFAULT_ANNOTATION_LINE_COLOR,
288289
annotationLineWidth = DEFAULT_ANNOTATION_LINE_WIDTH,
289290
annotationHVLineLimit = DEFAULT_ANNOTATION_HVLINE_LIMIT,
291+
cameraIsFixed = DEFAULT_CAMERA_IS_FIXED,
290292
} = initialProperties;
291293

292294
let currentWidth = width === AUTO ? 1 : width;
@@ -888,7 +890,7 @@ const createScatterplot = (
888890
};
889891

890892
const lassoEnd = (lassoPoints, lassoPointsFlat, { merge = false } = {}) => {
891-
camera.config({ isFixed: false });
893+
camera.config({ isFixed: cameraIsFixed });
892894
lassoPointsCurr = [...lassoPoints];
893895
const pointsInLasso = findPointsInLasso(lassoPointsFlat);
894896
select(pointsInLasso, { merge });
@@ -2722,7 +2724,7 @@ const createScatterplot = (
27222724
'transitionEnd',
27232725
() => {
27242726
resolve();
2725-
camera.config({ isFixed: false });
2727+
camera.config({ isFixed: cameraIsFixed });
27262728
},
27272729
1,
27282730
);
@@ -2921,6 +2923,11 @@ const createScatterplot = (
29212923
}
29222924
};
29232925

2926+
const setCameraIsFixed = (isFixed) => {
2927+
cameraIsFixed = Boolean(isFixed);
2928+
camera.config({ isFixed: cameraIsFixed });
2929+
};
2930+
29242931
const setLassoColor = (newLassoColor) => {
29252932
if (!newLassoColor) {
29262933
return;
@@ -3296,6 +3303,10 @@ const createScatterplot = (
32963303
return camera.view;
32973304
}
32983305

3306+
if (property === 'cameraIsFixed') {
3307+
return cameraIsFixed;
3308+
}
3309+
32993310
if (property === 'canvas') {
33003311
return canvas;
33013312
}
@@ -3616,6 +3627,10 @@ const createScatterplot = (
36163627
setCameraView(properties.cameraView);
36173628
}
36183629

3630+
if (properties.cameraIsFixed !== undefined) {
3631+
setCameraIsFixed(properties.cameraIsFixed);
3632+
}
3633+
36193634
if (properties.colorBy !== undefined) {
36203635
setColorBy(properties.colorBy);
36213636
}
@@ -3873,6 +3888,7 @@ const createScatterplot = (
38733888
const initCamera = () => {
38743889
if (!camera) {
38753890
camera = createDom2dCamera(canvas, {
3891+
isFixed: cameraIsFixed,
38763892
isPanInverted: [false, true],
38773893
defaultMouseDownMoveAction:
38783894
mouseMode === MOUSE_MODE_ROTATE ? 'rotate' : 'pan',
@@ -4083,7 +4099,7 @@ const createScatterplot = (
40834099
};
40844100

40854101
const cancelFrameListener = renderer.onFrame(() => {
4086-
// Update camera: this needs to happen on every
4102+
// Update camera: this needs to happen on every frame
40874103
isViewChanged = camera.tick();
40884104

40894105
if (!((isPointsDrawn || isAnnotationsDrawn) && (draw || isTransitioning))) {

src/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ interface BaseOptions {
164164
xScale: null | Scale;
165165
yScale: null | Scale;
166166
pointScaleMode: PointScaleMode;
167+
cameraIsFixed: boolean;
167168
}
168169

169170
// biome-ignore lint/style/useNamingConvention: KDBush is a library name

tests/constructor.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import createScatterplot, {
1010
} from '../src';
1111

1212
import {
13+
DEFAULT_CAMERA_IS_FIXED,
1314
DEFAULT_COLOR_NORMAL,
1415
DEFAULT_COLOR_ACTIVE,
1516
DEFAULT_COLOR_HOVER,
@@ -73,6 +74,7 @@ test('createScatterplot()', () => {
7374
expect(scatterplot.get('opacityInactiveScale')).toBe(DEFAULT_OPACITY_INACTIVE_SCALE);
7475
expect(scatterplot.get('width')).toBe(DEFAULT_WIDTH);
7576
expect(scatterplot.get('height')).toBe(DEFAULT_HEIGHT);
77+
expect(scatterplot.get('cameraIsFixed')).toBe(DEFAULT_CAMERA_IS_FIXED);
7678

7779
scatterplot.destroy();
7880
});

tests/get-set.test.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import '@babel/polyfill';
2+
import { nextAnimationFrame } from '@flekschas/utils';
23
import { assert, expect, test } from 'vitest';
34

45
import { version } from '../package.json';
@@ -451,7 +452,7 @@ test(
451452
);
452453

453454
expect(scatterplot.get('pointConnectionOpacityActive')).toBe(
454-
scatterplot.get('pointConnectionOpacityActive'),
455+
DEFAULT_POINT_CONNECTION_OPACITY_ACTIVE,
455456
);
456457

457458
scatterplot.set({
@@ -630,3 +631,63 @@ test(
630631
scatterplot.destroy();
631632
}
632633
);
634+
635+
test('set({ cameraIsFixed })', async () => {
636+
const canvas = createCanvas();
637+
const scatterplot = createScatterplot({ canvas });
638+
639+
await scatterplot.draw([
640+
[-1, 1],
641+
[1, 1],
642+
[0, 0],
643+
[-1, -1],
644+
[1, -1],
645+
]);
646+
647+
canvas.dispatchEvent(new WheelEvent('wheel', { deltaY: -100 }));
648+
649+
await nextAnimationFrame();
650+
651+
console.log('1. camera distance', scatterplot.get('camera').distance[0]);
652+
653+
// We expect the distance to be less than one because we zoomed into the plot
654+
// via wheeling
655+
expect(scatterplot.get('camera').distance[0]).toBeLessThan(1);
656+
657+
await scatterplot.zoomToOrigin();
658+
659+
expect(scatterplot.get('camera').distance[0]).toBe(1);
660+
661+
scatterplot.set({ cameraIsFixed: true });
662+
expect(scatterplot.get('cameraIsFixed')).toBe(true);
663+
664+
canvas.dispatchEvent(new WheelEvent('wheel', { deltaY: -100 }));
665+
666+
await nextAnimationFrame();
667+
668+
// We expect the distance to be one because we fixed the camera
669+
expect(scatterplot.get('camera').distance[0]).toBe(1);
670+
671+
scatterplot.set({ cameraIsFixed: false });
672+
expect(scatterplot.get('cameraIsFixed')).toBe(false);
673+
674+
canvas.dispatchEvent(new WheelEvent('wheel', { deltaY: -100 }));
675+
676+
await nextAnimationFrame();
677+
678+
// We expect the distance to be less than one because we unfixed the camera
679+
expect(scatterplot.get('camera').distance[0]).toBeLessThan(1);
680+
681+
await scatterplot.zoomToOrigin();
682+
expect(scatterplot.get('camera').distance[0]).toBe(1);
683+
684+
scatterplot.set({ cameraIsFixed: true });
685+
await scatterplot.zoomToPoints([2]);
686+
687+
// Even though the camera is fixed, programmatic zooming still works. Only
688+
// mouse wheel interactions are prevented
689+
expect(scatterplot.get('cameraIsFixed')).toBe(true);
690+
expect(scatterplot.get('camera').distance[0]).toBeLessThan(1);
691+
692+
scatterplot.destroy();
693+
});

0 commit comments

Comments
 (0)