mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-17 13:58:00 +08:00
* revert all commits to before Typescript migration * update compat workflow to match latest deps (#2335) * update compat workflow to match latest deps * attempt to debug * attempt to debug * remove debugging code * typo * update deps to match desktop (#2340) * fix: don't run lint with `--fix` on push tests (#2273) * fix: don't run lint with `--fix` on push tests * npx Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com> * rename X_approx_distribution to X_approximate_distribution (#2337) * Correctly handle non-finite numbers in heuristic determination of X distribution (#2342) * handle non-finites explicitly * improve and test edge case handling for distribution estimation * revert debugging changes * code readability * clean up type inferencing (#2332) * unit tests for 64 bit conversion * clean up type handling * type inference tests * more type inference fixes * use schema to determine user intent for data typing * stop using deprecated API * fbs type encoding test * add missing test * add more tests * correctly infer X type for CXG adaptor * lint * fix typo * ts migration * cleanup from PR review * lint * PR review changes * remove unused packages from client (#2359) * remove unused packages from client * add missing peer dep * fix: disable FE auth testing on compatibility tests (#2377) * update: release process (#2277) Co-authored-by: maniarathi <mani.arathi@gmail.com> * fix: remove spaces in param setup (#2380) * delete deploy workflow (#2396) * undo reformatting which now does not pass lint * fix snapshots which changed due to npm dep changes * add missing quoting to snapshot * another snapshot typo fix * TS Revert (2) - replay PR #2347 and #2354 (#2403) * replay edits from PR 2347 * TS Revert (3) - replay edits in PR #2327 (#2404) * replay edits in PR 2327 * TS Revert (4) - replay PR #2355 (#2405) * replay edits in PR 2355 * add additional babel config * reformat with new prettier config Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com> Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
181 lines
4.0 KiB
JavaScript
181 lines
4.0 KiB
JavaScript
import { vec2, mat3 } from "gl-matrix";
|
|
|
|
const EPSILON = 0.000001;
|
|
|
|
const scaleSpeed = 0.5;
|
|
const scaleMax = 3.0;
|
|
const scaleMin = 0.5;
|
|
const panBound = 0.8;
|
|
|
|
// private
|
|
const scratch0 = new Float32Array(16);
|
|
const scratch1 = new Float32Array(16);
|
|
|
|
function clamp(val, rng) {
|
|
return Math.max(Math.min(val, rng[1]), rng[0]);
|
|
}
|
|
|
|
class Camera {
|
|
constructor(canvas) {
|
|
this.prevEvent = {
|
|
clientX: 0,
|
|
clientY: 0,
|
|
type: 0,
|
|
};
|
|
this.canvas = canvas;
|
|
this.viewMatrix = mat3.create();
|
|
this.viewMatrixInv = mat3.create();
|
|
}
|
|
|
|
view() {
|
|
return this.viewMatrix;
|
|
}
|
|
|
|
invView() {
|
|
return this.viewMatrixInv;
|
|
}
|
|
|
|
distance() {
|
|
return this.viewMatrix[0];
|
|
}
|
|
|
|
pan(dx, dy) {
|
|
const m = this.viewMatrix;
|
|
const dyRange = [
|
|
-panBound - (m[7] + 1) / m[4],
|
|
panBound - (m[7] - 1) / m[4],
|
|
];
|
|
const dxRange = [
|
|
-panBound - (m[6] + 1) / m[0],
|
|
panBound - (m[6] - 1) / m[0],
|
|
];
|
|
|
|
const dxClamped = clamp(dx, dxRange);
|
|
const dyClamped = clamp(dy, dyRange);
|
|
if (Math.abs(dxClamped) <= EPSILON && Math.abs(dyClamped) <= EPSILON)
|
|
return;
|
|
|
|
mat3.translate(m, m, [dxClamped, dyClamped]);
|
|
mat3.invert(this.viewMatrixInv, m);
|
|
}
|
|
|
|
zoomAt(d, x = 0, y = 0) {
|
|
/*
|
|
Camera zoom at [x,y]
|
|
*/
|
|
const m = this.viewMatrix;
|
|
const bounds = [-panBound, panBound];
|
|
x = clamp(x, bounds);
|
|
y = clamp(y, bounds);
|
|
|
|
const dClamped = clamp(d * m[0], [scaleMin, scaleMax]) / m[0];
|
|
if (Math.abs(1 - dClamped) <= EPSILON) return; // noop request
|
|
|
|
mat3.translate(m, m, [x, y]);
|
|
mat3.scale(m, m, [dClamped, dClamped]);
|
|
mat3.translate(m, m, [-x, -y]);
|
|
|
|
mat3.invert(this.viewMatrixInv, m);
|
|
}
|
|
|
|
/*
|
|
Event handling
|
|
*/
|
|
|
|
flush(e) {
|
|
this.prevEvent.type = e.type;
|
|
this.prevEvent.clientX = e.clientX;
|
|
this.prevEvent.clientY = e.clientY;
|
|
}
|
|
|
|
localPosition(target, canvasX, canvasY, projectionInvTF) {
|
|
/*
|
|
Convert mouse position to local
|
|
*/
|
|
const { height, width } = target;
|
|
const targetRect = target.getBoundingClientRect();
|
|
canvasX -= targetRect.left;
|
|
canvasY -= targetRect.top;
|
|
|
|
const pos = vec2.fromValues(
|
|
2 * (canvasX / width) - 1,
|
|
-2 * (canvasY / height) + 1
|
|
);
|
|
if (projectionInvTF) {
|
|
vec2.transformMat3(pos, pos, projectionInvTF);
|
|
}
|
|
vec2.transformMat3(pos, pos, this.invView());
|
|
return pos;
|
|
}
|
|
|
|
mousePan(e, projectionTF) {
|
|
const projectionInvTF = mat3.invert(scratch0, projectionTF);
|
|
const pos = this.localPosition(
|
|
this.canvas,
|
|
e.clientX,
|
|
e.clientY,
|
|
projectionInvTF
|
|
);
|
|
const prev = this.localPosition(
|
|
this.canvas,
|
|
this.prevEvent.clientX,
|
|
this.prevEvent.clientY,
|
|
projectionInvTF
|
|
);
|
|
|
|
const delta = vec2.sub(scratch1, pos, prev);
|
|
this.pan(delta[0], delta[1]);
|
|
return true;
|
|
}
|
|
|
|
wheelZoom(e, projectionTF) {
|
|
const { height } = this.canvas;
|
|
const { deltaY, deltaMode, clientX, clientY } = e;
|
|
const scale = scaleSpeed * (deltaMode === 1 ? 12 : 1) * (deltaY || 0);
|
|
|
|
const projectionInvTF = mat3.invert(scratch0, projectionTF);
|
|
const pos = this.localPosition(
|
|
this.canvas,
|
|
clientX,
|
|
clientY,
|
|
projectionInvTF
|
|
);
|
|
this.zoomAt(1 / Math.exp(scale / height), pos[0], pos[1]);
|
|
return true;
|
|
}
|
|
|
|
handleEvent(e, projectionTF) {
|
|
/*
|
|
process the event, and return true if camera view changed
|
|
*/
|
|
let viewChanged = false;
|
|
switch (e.type) {
|
|
case "mousemove": {
|
|
/* eslint-disable-next-line no-bitwise --- MouseEvent.buttons exposes a bitmask, best acted on with bitops */
|
|
if (e.buttons & 0x1) {
|
|
viewChanged = this.mousePan(e, projectionTF);
|
|
}
|
|
this.flush(e);
|
|
break;
|
|
}
|
|
|
|
case "wheel": {
|
|
viewChanged = this.wheelZoom(e, projectionTF);
|
|
this.flush(e);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// noop
|
|
break;
|
|
}
|
|
return viewChanged;
|
|
}
|
|
}
|
|
|
|
function attachCamera(canvas) {
|
|
return new Camera(canvas);
|
|
}
|
|
|
|
export default attachCamera;
|