@pluot/react 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"Pluot.d.ts","sourceRoot":"","sources":["../src/Pluot.jsx"],"names":[],"mappings":"AAgDA,uCA+fC"}
1
+ {"version":3,"file":"Pluot.d.ts","sourceRoot":"","sources":["../src/Pluot.jsx"],"names":[],"mappings":"AAiDA,uCAqXC"}
package/dist-tsc/Pluot.js CHANGED
@@ -4,7 +4,7 @@ import { mat4, vec4 } from "gl-matrix";
4
4
  import lzs from "lz-string";
5
5
  import { isEqual, throttle } from "lodash-es";
6
6
  import { FetchStore } from 'zarrita';
7
- import { initialize, getIsWasmReady, render_wasm, pick_wasm, setStore, getStore, create2dCamera, create3dCamera, getBounds, getCameraMatrixFromBounds, checkWebGpuFeatureDetection, } from '@pluot/core';
7
+ import { initialize, getIsWasmReady, render_wasm, pick_wasm, setStore, getStore, getBounds, getCameraMatrixFromBounds, checkWebGpuFeatureDetection, onMouseMove2d, onWheel2d, onMouseMove3d, onWheel3d, } from '@pluot/core';
8
8
  // Needed due to "SyntaxError: Named export 'decompressFromUint8Array' not found.
9
9
  // The requested module 'lz-string' is a CommonJS module,
10
10
  // which may not support all module.exports as named exports."
@@ -37,7 +37,31 @@ export function Pluot(props) {
37
37
  const { width: widthProp, height: heightProp, plotId, plotType, store, storeName: storeNameProp, plotParams, viewMode = "2d", marginBottom = 100.0, marginLeft = 100.0, marginTop = 100.0, marginRight = 100.0, aspectRatioMode = "Contain", // "Ignore", "Contain", "Cover"
38
38
  aspectRatioAlignmentMode = "Start", // "Center", "Start", "End"
39
39
  format = "Raster", // "Raster", "Vector"
40
- minTimeout = 32, maxTimeout = 32, allowSimultaneousRenders = true, debugMargins = false, } = props;
40
+ minTimeout = 32, maxTimeout = 32, allowSimultaneousRenders = true, debugMargins = false, cameraMatrix: controlledCameraMatrix = null, setCameraMatrix: setControlledCameraMatrix = null, } = props;
41
+ // If cameraMatrix is not provided, then we manage the camera matrix internally.
42
+ const [uncontrolledCameraMatrix, setUncontrolledCameraMatrix] = useState(
43
+ // Note: We use an initializer function here to avoid
44
+ // sharing the same Float32Array among multiple Pluot
45
+ // component instances that may be rendered on the same page.
46
+ () => Float32Array.from(
47
+ // If the cameraMatrix prop was provided, use that for the initial camera matrix;
48
+ // otherwise use the default matrix.
49
+ controlledCameraMatrix === null
50
+ ? (viewMode === "2d" ? DEFAULT_VIEW : DEFAULT_3D_VIEW)
51
+ : controlledCameraMatrix));
52
+ // Decide which camera matrix and setter to use.
53
+ // If the user provides the cameraMatrix prop but NOT the setCameraMatrix setter,
54
+ // then interpret the prop as the "initial" camera settings, but still treat as uncontrolled.
55
+ const isControlledCamera = typeof setControlledCameraMatrix === "function";
56
+ // Alternatively, if the user provides the setCameraMatrix setter, but NOT
57
+ // the cameraMatrix, interpret this as they want to use the default camera
58
+ // value initially, but they still want a controlled camera matrix.
59
+ const cameraMatrix = isControlledCamera && controlledCameraMatrix !== null
60
+ ? controlledCameraMatrix
61
+ : uncontrolledCameraMatrix;
62
+ const setCameraMatrix = isControlledCamera
63
+ ? setControlledCameraMatrix
64
+ : setUncontrolledCameraMatrix;
41
65
  const width = Math.floor(widthProp);
42
66
  const height = Math.floor(heightProp);
43
67
  const isVector = format === "Vector";
@@ -57,7 +81,7 @@ export function Pluot(props) {
57
81
  const [supportsWebGpu, supportsWebGpuMessage] = useMemo(checkWebGpuFeatureDetection, []);
58
82
  const svgRef = useRef(null);
59
83
  const canvasRef = useRef(null);
60
- const cameraRef = useRef(null);
84
+ const cameraElementRef = useRef(null);
61
85
  const tempButtonRef = useRef(null);
62
86
  // We may want to update these things without triggering a re-render.
63
87
  const isRenderingRef = useRef(false);
@@ -71,195 +95,56 @@ export function Pluot(props) {
71
95
  const [didFirstRender, setDidFirstRender] = useState(false);
72
96
  const [bailedEarly, setBailedEarly] = useState(true);
73
97
  const [pickingResult, setPickingResult] = useState(null);
74
- // TODO: handle a viewMatrix that is provided and set via props,
75
- // to enable usage as a controlled component
76
- // (e.g., for linked views with shared cameras).
77
- const [viewMatrix, setViewMatrix] = useState(
78
- // Note: We use an initializer function here to avoid
79
- // sharing the same Float32Array among multiple Pluot
80
- // component instances that may be rendered on the same page.
81
- () => new Float32Array(DEFAULT_VIEW));
82
98
  useLayoutEffect(() => {
83
99
  initialize().then(() => setIsWasmReady(getIsWasmReady()));
84
100
  }, []);
85
- useEffect(() => {
86
- // Reset view matrix on plot change.
87
- // Create a new Float32Array to avoid sharing a mutable array
88
- // among multiple Pluot component instances.
89
- setViewMatrix(new Float32Array(viewMode === "2d" ? DEFAULT_VIEW : DEFAULT_3D_VIEW));
90
- //viewMatrixRef.current = new Float32Array(DEFAULT_VIEW);
91
- }, [plotId, viewMode]);
92
- // Set up the camera.
101
+ const wheelHandler = useEffectEvent((event) => {
102
+ const onWheel = viewMode === "3d" ? onWheel3d : onWheel2d;
103
+ const nextCameraMatrix = onWheel({
104
+ width,
105
+ height,
106
+ aspectRatioMode,
107
+ aspectRatioAlignmentMode,
108
+ margins: {
109
+ marginTop,
110
+ marginBottom,
111
+ marginLeft,
112
+ marginRight,
113
+ },
114
+ }, cameraMatrix, event);
115
+ setCameraMatrix(nextCameraMatrix);
116
+ });
117
+ const mouseMoveHandler = useEffectEvent((event) => {
118
+ const onMouseMove = viewMode === "3d" ? onMouseMove3d : onMouseMove2d;
119
+ const nextCameraMatrix = onMouseMove({
120
+ width,
121
+ height,
122
+ aspectRatioMode,
123
+ aspectRatioAlignmentMode,
124
+ margins: {
125
+ marginTop,
126
+ marginBottom,
127
+ marginLeft,
128
+ marginRight,
129
+ },
130
+ }, cameraMatrix, event);
131
+ setCameraMatrix(nextCameraMatrix);
132
+ });
133
+ // Set up FUNCTIONAL camera.
93
134
  useEffect(() => {
94
135
  // Set up the camera.
95
- const cameraEl = cameraRef.current;
136
+ const cameraEl = cameraElementRef.current;
96
137
  if (!cameraEl) {
97
138
  return () => { };
98
139
  }
99
- let dispose = () => { };
100
140
  // Create a 2D camera for handling zoom and pan.
101
- if (viewMode === "2d") {
102
- function onCameraEvent(camera, event) {
103
- camera.tick();
104
- // Reference: https://github.com/flekschas/regl-scatterplot/blob/17a650c352fad313d1574472b2fdc5f58b9e1eca/src/index.js#L1648
105
- setViewMatrix(prev => {
106
- // Since camera events happen even on mousemove events that do not change the matrix,
107
- // we check for equality here to avoid unnecessary state updates and plot re-renders.
108
- if (isEqual(prev, camera.view)) {
109
- return prev;
110
- }
111
- return mat4.clone(camera.view);
112
- });
113
- currentTimeout.current = minTimeout;
114
- }
115
- const camera = create2dCamera(cameraEl, {
116
- isFixed: false,
117
- distance: 0.0,
118
- //target: [0.0, 0.0],
119
- //viewCenter: [0.5, 0.5], // Should this be used when the coordinate system is (0 to 1) rather than (-1 to 1)?
120
- viewCenter: [0.0, 0.0],
121
- defaultMouseDownMoveAction: "pan",
122
- onKeyDown: (event) => {
123
- onCameraEvent(camera, event);
124
- },
125
- onKeyUp: (event) => {
126
- onCameraEvent(camera, event);
127
- },
128
- onMouseDown: (event) => {
129
- onCameraEvent(camera, event);
130
- },
131
- onMouseUp: (event) => {
132
- onCameraEvent(camera, event);
133
- },
134
- onMouseMove: (event) => {
135
- onCameraEvent(camera, event);
136
- },
137
- onWheel: (event) => {
138
- onCameraEvent(camera, event);
139
- },
140
- aspectRatioMode: aspectRatioMode,
141
- aspectRatioAlignmentMode: aspectRatioAlignmentMode,
142
- });
143
- // Set the initial view matrix.
144
- // We need to ensure we create a new copy of the array.
145
- camera.setView(new Float32Array(viewMatrix));
146
- const tempHandler = e => {
147
- // camera.setScaleBounds([[xScaleMin, xScaleMax], [yScaleMin, yScaleMax]])
148
- //camera.lookAt([2.0, 2.0], 2.0);
149
- //onCameraEvent(camera, null);
150
- // Only zoom/pan the X axis; keep Y unchanged
151
- const nextCameraMatrix = getCameraMatrixFromBounds({ yMin: 0.0, yMax: 100.0 }, new Float32Array(viewMatrix), {
152
- width,
153
- height,
154
- aspectRatioMode,
155
- aspectRatioAlignmentMode,
156
- margins: {
157
- marginTop,
158
- marginBottom,
159
- marginLeft,
160
- marginRight
161
- },
162
- });
163
- console.log("done", nextCameraMatrix);
164
- camera.setView(nextCameraMatrix);
165
- onCameraEvent(camera, null);
166
- };
167
- tempButtonRef.current.addEventListener('click', tempHandler);
168
- // Set up an onClick handler.
169
- //
170
- const clickHandler = (event) => {
171
- pickFrame(event.offsetX, event.offsetY);
172
- };
173
- cameraEl.addEventListener("click", clickHandler);
174
- dispose = () => {
175
- camera.dispose();
176
- cameraEl.removeEventListener("click", clickHandler);
177
- tempButtonRef.current.removeEventListener('click', tempHandler);
178
- };
179
- }
180
- else if (viewMode === "3d") {
181
- function onCameraEvent(camera, event) {
182
- camera.tick();
183
- // Note: the 3D camera stores the matrix in camera.matrix (not camera.view).
184
- setViewMatrix(prev => {
185
- // Since camera events happen even on mousemove events that do not change the matrix,
186
- // we check for equality here to avoid unnecessary state updates and plot re-renders.
187
- if (isEqual(prev, camera.matrix)) {
188
- return prev;
189
- }
190
- return mat4.clone(camera.matrix);
191
- });
192
- }
193
- const camera = create3dCamera(cameraEl, {
194
- mode: "orbit",
195
- zoomSpeed: -5,
196
- });
197
- // TODO:
198
- // - fork 3d-view-controls and remove usage of "global" - then clean up vite config.
199
- // - define a camera.dispsose option.
200
- // Reference: https://github.com/flekschas/dom-2d-camera/blob/cd59ea035a0ea72c2c0535fa3721f8127946576c/src/index.js#L237C3-L315C71
201
- const keyUpHandler = (event) => {
202
- // TODO
203
- };
204
- const keyDownHandler = (event) => {
205
- // TODO
206
- };
207
- const mouseUpHandler = (event) => {
208
- // TODO
209
- };
210
- const mouseDownHandler = (event) => {
211
- // TODO
212
- };
213
- // TODO: use react state?
214
- var lastX = 0;
215
- var lastY = 0;
216
- // Reference: https://github.com/mikolalysenko/3d-view/blob/8269e02337bba1923173a750aa7f3f0f76c91ba5/example/minimal.js#L67
217
- const mouseMoveHandler = (event) => {
218
- /*
219
- var dx = (event.clientX - lastX) / width;
220
- var dy = -(event.clientY - lastY) / height;
221
- if (event.which === 1) {
222
- if (event.shiftKey) {
223
- //zoom
224
- camera.rotate(now(), 0, 0, dx);
225
- } else {
226
- //rotate
227
- camera.rotate(now(), dx, dy);
228
- }
229
- } else if (event.which === 3) {
230
- //pan
231
- camera.pan(now(), dx, dy);
232
- }
233
- lastX = event.clientX;
234
- lastY = event.clientY;
235
- */
236
- onCameraEvent(camera, event);
237
- };
238
- const wheelHandler = (event) => {
239
- //camera.pan(now(), 0, 0, event.deltaY);
240
- onCameraEvent(camera, event);
241
- };
242
- cameraEl.addEventListener("keydown", keyDownHandler);
243
- cameraEl.addEventListener("keyup", keyUpHandler);
244
- cameraEl.addEventListener("mousedown", mouseDownHandler);
245
- cameraEl.addEventListener("mouseup", mouseUpHandler);
246
- cameraEl.addEventListener("mousemove", mouseMoveHandler);
247
- cameraEl.addEventListener("wheel", wheelHandler);
248
- dispose = () => {
249
- cameraEl.removeEventListener("keydown", keyDownHandler);
250
- cameraEl.removeEventListener("keyup", keyUpHandler);
251
- cameraEl.removeEventListener("mousedown", mouseDownHandler);
252
- cameraEl.removeEventListener("mouseup", mouseUpHandler);
253
- cameraEl.removeEventListener("mousemove", mouseMoveHandler);
254
- cameraEl.removeEventListener("wheel", wheelHandler);
255
- };
256
- //camera.matrix = new Float32Array(viewMatrix);
257
- }
258
- else {
259
- throw new Error("Unknown mode found.");
260
- }
261
- return dispose;
262
- }, [cameraRef, viewMode, aspectRatioMode, aspectRatioAlignmentMode, width, height, marginLeft, marginRight, marginTop, marginBottom]);
141
+ cameraEl.addEventListener("mousemove", mouseMoveHandler);
142
+ cameraEl.addEventListener("wheel", wheelHandler);
143
+ return () => {
144
+ cameraEl.removeEventListener("mousemove", mouseMoveHandler);
145
+ cameraEl.removeEventListener("wheel", wheelHandler);
146
+ };
147
+ }, [viewMode]);
263
148
  // The picking callback.
264
149
  const pickFrame = useEffectEvent(async (screenCoordX, screenCoordY) => {
265
150
  const renderParams = {
@@ -276,7 +161,7 @@ export function Pluot(props) {
276
161
  view_mode: viewMode,
277
162
  pickable: false,
278
163
  // Should see the latest viewMatrix here, since renderFrame is wrapped in useEffectEvent.
279
- camera_view: viewMatrix,
164
+ camera_view: cameraMatrix,
280
165
  plot_id: plotId,
281
166
  plot_type: plotType,
282
167
  store_name: storeName,
@@ -314,7 +199,7 @@ export function Pluot(props) {
314
199
  view_mode: viewMode,
315
200
  pickable: false,
316
201
  // Should see the latest viewMatrix here, since renderFrame is wrapped in useEffectEvent.
317
- camera_view: viewMatrix,
202
+ camera_view: cameraMatrix,
318
203
  plot_id: plotId,
319
204
  plot_type: plotType,
320
205
  store_name: storeName,
@@ -404,9 +289,9 @@ export function Pluot(props) {
404
289
  }
405
290
  // Render on the next animation frame.
406
291
  throttledRender();
407
- }, [isWasmReady, didFirstRender, viewMatrix, backlogIteration, plotId, plotType, plotParams, storeName, format,
292
+ }, [isWasmReady, didFirstRender, cameraMatrix, backlogIteration, plotId, plotType, plotParams, storeName, format,
408
293
  width, height, aspectRatioMode, aspectRatioAlignmentMode, marginLeft, marginRight, marginTop, marginBottom]);
409
- return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { width, height, position: "relative" }, children: [!supportsWebGpu ? (_jsx("p", { children: supportsWebGpuMessage })) : null, _jsx("div", { ref: cameraRef, style: {
294
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { width, height, position: "relative" }, children: [!supportsWebGpu ? (_jsx("p", { children: supportsWebGpuMessage })) : null, _jsx("div", { ref: cameraElementRef, style: {
410
295
  position: "absolute",
411
296
  top: marginTop,
412
297
  left: marginLeft,
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@pluot/react",
3
3
  "private": false,
4
- "version": "0.1.0",
5
- "description": "",
4
+ "version": "0.1.1",
5
+ "description": "React component for visualization with Pluot",
6
6
  "license": "Apache-2.0",
7
+ "author": "Mark Keller",
8
+ "homepage": "https://pluot.dev",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/keller-mark/pluot.git"
12
+ },
7
13
  "type": "module",
8
14
  "main": "dist/index.js",
9
15
  "files": [
@@ -30,7 +36,7 @@
30
36
  "mouse-wheel": "^1.0.2",
31
37
  "lz-string": "^1.5.0",
32
38
  "lodash-es": "^4.17.23",
33
- "@pluot/core": "0.1.0"
39
+ "@pluot/core": "0.1.1"
34
40
  },
35
41
  "devDependencies": {
36
42
  "vitest": "^3.2.4",