@vitessce/neuroglancer 3.6.18 → 3.7.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.
- package/dist/{ReactNeuroglancer-Crquekcy.js → ReactNeuroglancer-C0i-a6Cw.js} +717 -1268
- package/dist/{index-BNCfoEHv.js → index-w8xI9TWU.js} +2211 -478
- package/dist/index.js +1 -1
- package/dist-tsc/Neuroglancer.d.ts +5 -3
- package/dist-tsc/Neuroglancer.d.ts.map +1 -1
- package/dist-tsc/Neuroglancer.js +31 -72
- package/dist-tsc/NeuroglancerSubscriber.d.ts.map +1 -1
- package/dist-tsc/NeuroglancerSubscriber.js +329 -93
- package/dist-tsc/ReactNeuroglancer.d.ts +147 -2
- package/dist-tsc/ReactNeuroglancer.d.ts.map +1 -1
- package/dist-tsc/ReactNeuroglancer.js +819 -5
- package/dist-tsc/styles.d.ts.map +1 -1
- package/dist-tsc/styles.js +3 -1
- package/dist-tsc/utils.d.ts +41 -0
- package/dist-tsc/utils.d.ts.map +1 -0
- package/dist-tsc/utils.js +117 -0
- package/dist-tsc/utils.test.d.ts +2 -0
- package/dist-tsc/utils.test.d.ts.map +1 -0
- package/dist-tsc/utils.test.js +34 -0
- package/package.json +12 -12
- package/src/Neuroglancer.js +32 -91
- package/src/NeuroglancerSubscriber.js +400 -108
- package/src/ReactNeuroglancer.js +912 -6
- package/src/styles.js +3 -1
- package/src/utils.js +156 -0
- package/src/utils.test.js +44 -0
package/dist-tsc/styles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.js"],"names":[],"mappings":"AAqkFA,gIAcC;AA/kFD;;cAiBe,CAAC;;;kBAIP,eAAsB;gBAChB,WAAU;eAAsB,WACvC;EAlBJ"}
|
package/dist-tsc/styles.js
CHANGED
|
@@ -21,6 +21,7 @@ const globalNeuroglancerCss = `
|
|
|
21
21
|
.neuroglancer-viewer-top-row,
|
|
22
22
|
.neuroglancer-layer-panel,
|
|
23
23
|
.neuroglancer-side-panel-column,
|
|
24
|
+
.neuroglancer-display-dimensions-widget,
|
|
24
25
|
.neuroglancer-data-panel-layout-controls button{
|
|
25
26
|
display: none !important;
|
|
26
27
|
}
|
|
@@ -1423,7 +1424,8 @@ const globalNeuroglancerStyles = {
|
|
|
1423
1424
|
borderColor: '#000',
|
|
1424
1425
|
borderWidth: '2px',
|
|
1425
1426
|
},
|
|
1426
|
-
|
|
1427
|
+
// Hides the white border around NG view that shows the view is focused
|
|
1428
|
+
// '.neuroglancer-panel:focus-within': { borderColor: '#fff' },
|
|
1427
1429
|
'.neuroglancer-layer-group-viewer': { outline: '0px' },
|
|
1428
1430
|
'.neuroglancer-layer-group-viewer-context-menu': {
|
|
1429
1431
|
flexDirection: 'column',
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if the difference is greater than the epsilon for that key.
|
|
3
|
+
* @param {array | number} a Previous viewerState key, i.e., position.
|
|
4
|
+
* @param {array | number } b Next viewerState key, i.e., position.
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export function valueGreaterThanEpsilon(a: array | number, b: array | number, epsilon: any): boolean | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Returns true if the two states are equal, or false if not.
|
|
10
|
+
* @param {object} prevState Previous viewer state.
|
|
11
|
+
* @param {object} nextState Next viewer state.
|
|
12
|
+
* @returns {Boolean} True if any key has changed
|
|
13
|
+
*/
|
|
14
|
+
export function didCameraStateChange(prevState: object, nextState: object): boolean;
|
|
15
|
+
export function diffCameraState(prev: any, next: any): {
|
|
16
|
+
changed: boolean | undefined;
|
|
17
|
+
scale: boolean | undefined;
|
|
18
|
+
pos: boolean | undefined;
|
|
19
|
+
rot: boolean | undefined;
|
|
20
|
+
};
|
|
21
|
+
export function quaternionToEuler([x, y, z, w]: [any, any, any, any]): any[];
|
|
22
|
+
export function eulerToQuaternion(pitch: any, yaw: any, roll?: number): any[];
|
|
23
|
+
export function makeVitNgZoomCalibrator(initialNgProjectionScale: any, initialDeckZoom?: number): {
|
|
24
|
+
base: number;
|
|
25
|
+
vitToNgZoom(z: any): number;
|
|
26
|
+
ngToVitZoom(sNg: any): number;
|
|
27
|
+
};
|
|
28
|
+
export namespace EPSILON_KEYS_MAPPING_NG {
|
|
29
|
+
let projectionScale: number;
|
|
30
|
+
let projectionOrientation: number;
|
|
31
|
+
let position: number;
|
|
32
|
+
}
|
|
33
|
+
export const SOFT_POS_FACTOR: 0.15;
|
|
34
|
+
export const Q_Y_UP: number[];
|
|
35
|
+
export function multiplyQuat(a: any, b: any): number[];
|
|
36
|
+
export function conjQuat(q: any): any[];
|
|
37
|
+
export function quatdotAbs(a: any, b: any): number;
|
|
38
|
+
export function rad2deg(r: any): number;
|
|
39
|
+
export function deg2rad(d: any): number;
|
|
40
|
+
export function nearEq(a: any, b: any, epsilon: any): boolean;
|
|
41
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AA8DA;;;;;GAKG;AAEH,2CALW,KAAK,GAAG,MAAM,KACd,KAAK,GAAG,MAAM,qCAYxB;AAMD;;;;;GAKG;AAEH,gDALW,MAAM,aACN,MAAM,WAYhB;AAID;;;;;EAgBC;AAID,6EAUC;AAID,8EAIC;AAID;;;;EAWC;;;;;;AA3ID,mCAAoC;AAEpC,8BAAmC;AAG5B,uDASN;AAEM,wCAAmD;AAGnD,mDAA4F;AAG5F,wCAAsC;AACtC,wCAAsC;AAwCtC,8DAEN"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Quaternion, Euler, } from 'three';
|
|
2
|
+
// For now deckGl uses degrees, but if changes to radian can change here
|
|
3
|
+
// const VIT_UNITS = 'degrees';
|
|
4
|
+
export const EPSILON_KEYS_MAPPING_NG = {
|
|
5
|
+
projectionScale: 100,
|
|
6
|
+
projectionOrientation: 2e-2,
|
|
7
|
+
position: 1,
|
|
8
|
+
};
|
|
9
|
+
// allow smaller pos deltas to pass when zoom changed
|
|
10
|
+
export const SOFT_POS_FACTOR = 0.15;
|
|
11
|
+
// To rotate the y-axis up in NG
|
|
12
|
+
export const Q_Y_UP = [1, 0, 0, 0]; // [x,y,z,w] for 180° about X
|
|
13
|
+
// ---- Y-up correction: 180° around X so X stays right, Y flips up (Z flips sign, which is OK) ----
|
|
14
|
+
export const multiplyQuat = (a, b) => {
|
|
15
|
+
const [ax, ay, az, aw] = a;
|
|
16
|
+
const [bx, by, bz, bw] = b;
|
|
17
|
+
return [
|
|
18
|
+
aw * bx + ax * bw + ay * bz - az * by,
|
|
19
|
+
aw * by - ax * bz + ay * bw + az * bx,
|
|
20
|
+
aw * bz + ax * by - ay * bx + az * bw,
|
|
21
|
+
aw * bw - ax * bx - ay * by - az * bz,
|
|
22
|
+
];
|
|
23
|
+
};
|
|
24
|
+
export const conjQuat = q => ([-q[0], -q[1], -q[2], q[3]]); // inverse for unit quats
|
|
25
|
+
// Helper function to compute the cosine dot product of two quaternion
|
|
26
|
+
export const quatdotAbs = (a, b) => Math.abs(a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]);
|
|
27
|
+
export const rad2deg = r => r * 180 / Math.PI;
|
|
28
|
+
export const deg2rad = d => d * Math.PI / 180;
|
|
29
|
+
// export const toVitUnits = rad => VIT_UNITS === 'degrees' ? (rad * 180 / Math.PI) : rad;
|
|
30
|
+
// export const fromVitUnits = val => VIT_UNITS === 'degrees' ? (val * Math.PI / 180) : val;
|
|
31
|
+
/**
|
|
32
|
+
* Is this a valid viewerState object?
|
|
33
|
+
* @param {object} viewerState
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function isValidState(viewerState) {
|
|
37
|
+
const { projectionScale, projectionOrientation, position, dimensions } = viewerState || {};
|
|
38
|
+
return (dimensions !== undefined
|
|
39
|
+
&& typeof projectionScale === 'number'
|
|
40
|
+
&& Array.isArray(projectionOrientation)
|
|
41
|
+
&& projectionOrientation.length === 4
|
|
42
|
+
&& Array.isArray(position)
|
|
43
|
+
&& position.length === 3);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns true if the difference is greater than the epsilon for that key.
|
|
47
|
+
* @param {array | number} a Previous viewerState key, i.e., position.
|
|
48
|
+
* @param {array | number } b Next viewerState key, i.e., position.
|
|
49
|
+
* @returns
|
|
50
|
+
*/
|
|
51
|
+
export function valueGreaterThanEpsilon(a, b, epsilon) {
|
|
52
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
53
|
+
return a.some((val, i) => Math.abs(val - b[i]) > epsilon);
|
|
54
|
+
}
|
|
55
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
56
|
+
return Math.abs(a - b) > epsilon;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
export const nearEq = (a, b, epsilon) => (Number.isFinite(a) && Number.isFinite(b) ? Math.abs(a - b) <= epsilon : a === b);
|
|
61
|
+
/**
|
|
62
|
+
* Returns true if the two states are equal, or false if not.
|
|
63
|
+
* @param {object} prevState Previous viewer state.
|
|
64
|
+
* @param {object} nextState Next viewer state.
|
|
65
|
+
* @returns {Boolean} True if any key has changed
|
|
66
|
+
*/
|
|
67
|
+
export function didCameraStateChange(prevState, nextState) {
|
|
68
|
+
if (!isValidState(nextState))
|
|
69
|
+
return false;
|
|
70
|
+
return Object.entries(EPSILON_KEYS_MAPPING_NG)
|
|
71
|
+
.some(([key, eps]) => valueGreaterThanEpsilon(prevState?.[key], nextState?.[key], eps));
|
|
72
|
+
}
|
|
73
|
+
// To see if any and which cameraState has changed
|
|
74
|
+
// adjust for coupled zoom+position changes
|
|
75
|
+
export function diffCameraState(prev, next) {
|
|
76
|
+
if (!isValidState(next))
|
|
77
|
+
return { changed: false, scale: false, pos: false, rot: false };
|
|
78
|
+
const eps = EPSILON_KEYS_MAPPING_NG;
|
|
79
|
+
const scale = valueGreaterThanEpsilon(prev?.projectionScale, next?.projectionScale, eps.projectionScale);
|
|
80
|
+
const posHard = valueGreaterThanEpsilon(prev?.position, next?.position, eps.position);
|
|
81
|
+
const rot = valueGreaterThanEpsilon(prev?.projectionOrientation, next?.projectionOrientation, eps.projectionOrientation);
|
|
82
|
+
// If zoom changed, allow a softer position threshold so zoom+pos travel together.
|
|
83
|
+
const posSoft = !posHard && scale
|
|
84
|
+
&& valueGreaterThanEpsilon(prev?.position, next?.position, SOFT_POS_FACTOR);
|
|
85
|
+
const pos = posHard || posSoft;
|
|
86
|
+
return { changed: scale || pos || rot, scale, pos, rot };
|
|
87
|
+
}
|
|
88
|
+
// Convert WebGL's Quaternion rotation to DeckGL's Euler
|
|
89
|
+
export function quaternionToEuler([x, y, z, w]) {
|
|
90
|
+
const quaternion = new Quaternion(x, y, z, w);
|
|
91
|
+
// deck.gl uses Y (yaw), X (pitch), Z (roll)
|
|
92
|
+
// TODO confirm the direction - YXZ
|
|
93
|
+
const euler = new Euler().setFromQuaternion(quaternion, 'YXZ');
|
|
94
|
+
const pitch = euler.x; // X-axis rotation
|
|
95
|
+
const yaw = euler.y; // Y-axis rotation
|
|
96
|
+
// return [pitch * RAD2DEG, yaw * RAD2DEG];
|
|
97
|
+
return [pitch, yaw];
|
|
98
|
+
}
|
|
99
|
+
// Convert DeckGL's rotation in Euler to WebGL's Quaternion
|
|
100
|
+
export function eulerToQuaternion(pitch, yaw, roll = 0) {
|
|
101
|
+
const euler = new Euler(pitch, yaw, roll, 'YXZ'); // rotation order
|
|
102
|
+
const quaternion = new Quaternion().setFromEuler(euler);
|
|
103
|
+
return [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
|
|
104
|
+
}
|
|
105
|
+
// Calibrate once from an initial deck zoom and NG projectionScale
|
|
106
|
+
export function makeVitNgZoomCalibrator(initialNgProjectionScale, initialDeckZoom = 0) {
|
|
107
|
+
const base = (initialNgProjectionScale / (2 ** -initialDeckZoom));
|
|
108
|
+
return {
|
|
109
|
+
base,
|
|
110
|
+
vitToNgZoom(z) {
|
|
111
|
+
return (base) * (2 ** -z);
|
|
112
|
+
},
|
|
113
|
+
ngToVitZoom(sNg) {
|
|
114
|
+
return Math.log2(base / (sNg));
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../src/utils.test.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { multiplyQuat, conjQuat, eulerToQuaternion, quaternionToEuler, Q_Y_UP, } from './utils.js';
|
|
3
|
+
// To normalize/map the angels between [-π, π]
|
|
4
|
+
const wrap = a => Math.atan2(Math.sin(a), Math.cos(a));
|
|
5
|
+
const close = (a, b, eps = 1e-6) => Math.abs(wrap(a - b)) < eps;
|
|
6
|
+
describe('Quaternion utilities', () => {
|
|
7
|
+
it('multiplyQuat identity', () => {
|
|
8
|
+
const I = [0, 0, 0, 1];
|
|
9
|
+
const q = eulerToQuaternion(0.2, -0.5, 0.1);
|
|
10
|
+
expect(multiplyQuat(I, q)).toEqual(expect.arrayContaining(q));
|
|
11
|
+
expect(multiplyQuat(q, I)).toEqual(expect.arrayContaining(q));
|
|
12
|
+
});
|
|
13
|
+
// Tests the expected angle after Q_Y_UP
|
|
14
|
+
it('Y-up flip maps (x, y, z) -> (x, -y, -z)', () => {
|
|
15
|
+
const v = [0.3, 0.4, 0.0];
|
|
16
|
+
const qVit = eulerToQuaternion(...v);
|
|
17
|
+
const qNg = multiplyQuat(Q_Y_UP, qVit);
|
|
18
|
+
const [pitch, yaw] = quaternionToEuler(qNg); // radians
|
|
19
|
+
const ok = (close(pitch, -v[0]) && close(yaw, -v[1]))
|
|
20
|
+
|| (close(pitch, -v[0]) && close(yaw, Math.PI - v[1]));
|
|
21
|
+
// Euler angles can give both yaw’ ≈ −yaw and yaw’ ≈ π − yaw
|
|
22
|
+
// Alternative is to compare only Quaternion
|
|
23
|
+
expect(ok).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
// Tests that applying and reversing Q_Y_UP gives back original orientation
|
|
26
|
+
it('Q_Y_UP round trip', () => {
|
|
27
|
+
const qVit = eulerToQuaternion(0.25, -0.7, 0);
|
|
28
|
+
const qNg = multiplyQuat(Q_Y_UP, qVit);
|
|
29
|
+
const qBack = multiplyQuat(conjQuat(Q_Y_UP), qNg);
|
|
30
|
+
// equal up to sign
|
|
31
|
+
const sameOrNeg = qBack.map((x, i) => Math.abs(x) - Math.abs(qVit[i]));
|
|
32
|
+
sameOrNeg.forEach(d => expect(Math.abs(d)).toBeLessThan(1e-6));
|
|
33
|
+
});
|
|
34
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/neuroglancer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.1",
|
|
4
4
|
"author": "Gehlenborg Lab",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -16,27 +16,27 @@
|
|
|
16
16
|
"dist-tsc"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@janelia-flyem/react-neuroglancer": "2.5.0",
|
|
20
19
|
"@janelia-flyem/neuroglancer": "2.37.5",
|
|
21
20
|
"lodash-es": "^4.17.21",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"@vitessce/
|
|
25
|
-
"@vitessce/
|
|
26
|
-
"@vitessce/
|
|
27
|
-
"@vitessce/
|
|
28
|
-
"@vitessce/
|
|
21
|
+
"three": "^0.154.0",
|
|
22
|
+
"react": "^18.0.0",
|
|
23
|
+
"@vitessce/neuroglancer-workers": "3.7.1",
|
|
24
|
+
"@vitessce/styles": "3.7.1",
|
|
25
|
+
"@vitessce/constants-internal": "3.7.1",
|
|
26
|
+
"@vitessce/vit-s": "3.7.1",
|
|
27
|
+
"@vitessce/sets-utils": "3.7.1",
|
|
28
|
+
"@vitessce/utils": "3.7.1",
|
|
29
|
+
"@vitessce/tooltip": "3.7.1"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@testing-library/jest-dom": "^6.6.3",
|
|
32
33
|
"@testing-library/react": "^16.3.0",
|
|
33
|
-
"react": "^18.0.0",
|
|
34
34
|
"react-dom": "^18.0.0",
|
|
35
|
-
"vite": "^
|
|
35
|
+
"vite": "^7.0.0",
|
|
36
36
|
"vitest": "^3.1.4"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"bundle": "pnpm exec vite build -c ../../../scripts/vite.config.
|
|
39
|
+
"bundle": "pnpm exec vite build -c ../../../scripts/vite.config.mjs",
|
|
40
40
|
"test": "pnpm exec vitest --run"
|
|
41
41
|
},
|
|
42
42
|
"module": "dist/index.js",
|
package/src/Neuroglancer.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* eslint-disable react-refresh/only-export-components */
|
|
2
2
|
import React, { PureComponent, Suspense } from 'react';
|
|
3
3
|
import { ChunkWorker } from '@vitessce/neuroglancer-workers';
|
|
4
|
-
import { isEqualWith, pick } from 'lodash-es';
|
|
5
4
|
import { NeuroglancerGlobalStyles } from './styles.js';
|
|
6
5
|
|
|
7
6
|
const LazyReactNeuroglancer = React.lazy(() => import('./ReactNeuroglancer.js'));
|
|
@@ -9,145 +8,86 @@ const LazyReactNeuroglancer = React.lazy(() => import('./ReactNeuroglancer.js'))
|
|
|
9
8
|
function createWorker() {
|
|
10
9
|
return new ChunkWorker();
|
|
11
10
|
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Is this a valid viewerState object?
|
|
15
|
-
* @param {object} viewerState
|
|
16
|
-
* @returns {boolean}
|
|
17
|
-
*/
|
|
18
|
-
function isValidState(viewerState) {
|
|
19
|
-
const { projectionScale, projectionOrientation, position, dimensions } = viewerState || {};
|
|
20
|
-
return (
|
|
21
|
-
dimensions !== undefined
|
|
22
|
-
&& typeof projectionScale === 'number'
|
|
23
|
-
&& Array.isArray(projectionOrientation)
|
|
24
|
-
&& projectionOrientation.length === 4
|
|
25
|
-
&& Array.isArray(position)
|
|
26
|
-
&& position.length === 3
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// TODO: Do we want to use the same epsilon value
|
|
31
|
-
// for every viewstate property being compared?
|
|
32
|
-
const EPSILON = 1e-7;
|
|
33
|
-
const VIEWSTATE_KEYS = ['projectionScale', 'projectionOrientation', 'position'];
|
|
34
|
-
|
|
35
|
-
// Custom numeric comparison function
|
|
36
|
-
// for isEqualWith, to be able to set a custom epsilon.
|
|
37
|
-
function customizer(a, b) {
|
|
38
|
-
if (typeof a === 'number' && typeof b === 'number') {
|
|
39
|
-
// Returns true if the values are equivalent, else false.
|
|
40
|
-
return Math.abs(a - b) > EPSILON;
|
|
41
|
-
}
|
|
42
|
-
// Return undefined to fallback to the default
|
|
43
|
-
// comparison function.
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Returns true if the two states are equal, or false if not.
|
|
49
|
-
* @param {object} prevState Previous viewer state.
|
|
50
|
-
* @param {object} nextState Next viewer state.
|
|
51
|
-
* @returns
|
|
52
|
-
*/
|
|
53
|
-
function compareViewerState(prevState, nextState) {
|
|
54
|
-
if (isValidState(nextState)) {
|
|
55
|
-
// Subset the viewerState objects to only the keys
|
|
56
|
-
// that we want to use for comparison.
|
|
57
|
-
const prevSubset = pick(prevState, VIEWSTATE_KEYS);
|
|
58
|
-
const nextSubset = pick(nextState, VIEWSTATE_KEYS);
|
|
59
|
-
return isEqualWith(prevSubset, nextSubset, customizer);
|
|
60
|
-
}
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class Neuroglancer extends PureComponent {
|
|
11
|
+
export class NeuroglancerComp extends PureComponent {
|
|
65
12
|
constructor(props) {
|
|
66
13
|
super(props);
|
|
67
|
-
|
|
68
14
|
this.bundleRoot = createWorker();
|
|
69
|
-
|
|
70
|
-
this.viewerState = props.viewerState;
|
|
15
|
+
this.cellColorMapping = props.cellColorMapping;
|
|
71
16
|
this.justReceivedExternalUpdate = false;
|
|
72
|
-
|
|
73
17
|
this.prevElement = null;
|
|
74
18
|
this.prevClickHandler = null;
|
|
75
19
|
this.prevMouseStateChanged = null;
|
|
76
20
|
this.prevHoverHandler = null;
|
|
77
|
-
|
|
78
21
|
this.onViewerStateChanged = this.onViewerStateChanged.bind(this);
|
|
79
22
|
this.onRef = this.onRef.bind(this);
|
|
23
|
+
// To avoid closure for onSegmentClick(), to update the selection
|
|
24
|
+
this.latestOnSegmentClick = props.onSegmentClick;
|
|
25
|
+
this.latestOnSelectHoveredCoords = props.onSelectHoveredCoords;
|
|
80
26
|
}
|
|
81
27
|
|
|
82
28
|
onRef(viewerRef) {
|
|
83
29
|
// Here, we have access to the viewerRef.viewer object,
|
|
84
30
|
// which we can use to add/remove event handlers.
|
|
85
|
-
const {
|
|
86
|
-
onSegmentClick,
|
|
87
|
-
onSelectHoveredCoords,
|
|
88
|
-
} = this.props;
|
|
89
31
|
|
|
90
32
|
if (viewerRef) {
|
|
91
33
|
// Mount
|
|
92
34
|
const { viewer } = viewerRef;
|
|
93
35
|
this.prevElement = viewer.element;
|
|
94
36
|
this.prevMouseStateChanged = viewer.mouseState.changed;
|
|
37
|
+
viewer.inputEventBindings.sliceView.set('at:dblclick0', () => {});
|
|
38
|
+
viewer.inputEventBindings.perspectiveView.set('at:dblclick0', () => {});
|
|
95
39
|
this.prevClickHandler = (event) => {
|
|
96
40
|
if (event.button === 0) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
41
|
+
// Wait for mouseState to update
|
|
42
|
+
requestAnimationFrame(() => {
|
|
43
|
+
const { pickedValue, pickedRenderLayer } = viewer.mouseState;
|
|
44
|
+
// Only trigger selection when a segment is clicked rather than any click on the view
|
|
45
|
+
if (pickedValue && pickedValue.low !== undefined && pickedRenderLayer) {
|
|
46
|
+
this.latestOnSegmentClick?.(pickedValue.low);
|
|
101
47
|
}
|
|
102
|
-
}
|
|
48
|
+
});
|
|
103
49
|
}
|
|
104
50
|
};
|
|
105
|
-
viewer.element.addEventListener('mousedown', this.prevClickHandler);
|
|
106
|
-
|
|
107
51
|
this.prevHoverHandler = () => {
|
|
108
52
|
if (viewer.mouseState.pickedValue !== undefined) {
|
|
109
53
|
const pickedSegment = viewer.mouseState.pickedValue;
|
|
110
|
-
|
|
54
|
+
this.latestOnSelectHoveredCoords?.(pickedSegment?.low);
|
|
111
55
|
}
|
|
112
56
|
};
|
|
113
|
-
|
|
57
|
+
viewer.element.addEventListener('mouseup', this.prevClickHandler);
|
|
114
58
|
viewer.mouseState.changed.add(this.prevHoverHandler);
|
|
115
59
|
} else {
|
|
116
60
|
// Unmount (viewerRef is null)
|
|
117
61
|
if (this.prevElement && this.prevClickHandler) {
|
|
118
|
-
this.prevElement.removeEventListener('
|
|
62
|
+
this.prevElement.removeEventListener('mouseup', this.prevClickHandler);
|
|
63
|
+
this.prevClickHandler = null;
|
|
119
64
|
}
|
|
120
65
|
if (this.prevMouseStateChanged && this.prevHoverHandler) {
|
|
121
66
|
this.prevMouseStateChanged.remove(this.prevHoverHandler);
|
|
67
|
+
this.prevHoverHandler = null;
|
|
122
68
|
}
|
|
69
|
+
this.prevElement = null;
|
|
70
|
+
this.prevMouseStateChanged = null;
|
|
123
71
|
}
|
|
124
72
|
}
|
|
125
73
|
|
|
126
74
|
onViewerStateChanged(nextState) {
|
|
127
75
|
const { setViewerState } = this.props;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!this.justReceivedExternalUpdate && !compareViewerState(prevState, nextState)) {
|
|
131
|
-
this.viewerState = nextState;
|
|
132
|
-
this.justReceivedExternalUpdate = false;
|
|
133
|
-
setViewerState(nextState);
|
|
134
|
-
}
|
|
76
|
+
setViewerState(nextState);
|
|
135
77
|
}
|
|
136
78
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
79
|
+
componentDidUpdate(prevProps) {
|
|
80
|
+
const { onSegmentClick, onSelectHoveredCoords } = this.props;
|
|
81
|
+
if (prevProps.onSegmentClick !== onSegmentClick) {
|
|
82
|
+
this.latestOnSegmentClick = onSegmentClick;
|
|
83
|
+
}
|
|
84
|
+
if (prevProps.onSelectHoveredCoords !== onSelectHoveredCoords) {
|
|
85
|
+
this.latestOnSelectHoveredCoords = onSelectHoveredCoords;
|
|
144
86
|
}
|
|
145
87
|
}
|
|
146
88
|
|
|
147
89
|
render() {
|
|
148
|
-
const {
|
|
149
|
-
classes,
|
|
150
|
-
} = this.props;
|
|
90
|
+
const { classes, viewerState, cellColorMapping } = this.props;
|
|
151
91
|
|
|
152
92
|
return (
|
|
153
93
|
<>
|
|
@@ -156,9 +96,10 @@ export class Neuroglancer extends PureComponent {
|
|
|
156
96
|
<Suspense fallback={<div>Loading...</div>}>
|
|
157
97
|
<LazyReactNeuroglancer
|
|
158
98
|
brainMapsClientId="NOT_A_VALID_ID"
|
|
159
|
-
viewerState={
|
|
99
|
+
viewerState={viewerState}
|
|
160
100
|
onViewerStateChanged={this.onViewerStateChanged}
|
|
161
101
|
bundleRoot={this.bundleRoot}
|
|
102
|
+
cellColorMapping={cellColorMapping}
|
|
162
103
|
ref={this.onRef}
|
|
163
104
|
/>
|
|
164
105
|
</Suspense>
|