@pluot/react 0.1.16 → 0.1.17
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/index.js +994 -100
- package/dist-tsc/BrushOverlay.d.ts +31 -0
- package/dist-tsc/BrushOverlay.d.ts.map +1 -0
- package/dist-tsc/BrushOverlay.js +91 -0
- package/dist-tsc/Pluot.d.ts +2 -1
- package/dist-tsc/Pluot.d.ts.map +1 -1
- package/dist-tsc/Pluot.js +59 -22
- package/dist-tsc/Tooltip.d.ts +2 -1
- package/dist-tsc/Tooltip.d.ts.map +1 -1
- package/dist-tsc/Tooltip.js +15 -1
- package/dist-tsc/brush.d.ts +155 -0
- package/dist-tsc/brush.d.ts.map +1 -0
- package/dist-tsc/brush.js +312 -0
- package/dist-tsc/brush.test.d.ts +2 -0
- package/dist-tsc/brush.test.d.ts.map +1 -0
- package/dist-tsc/brush.test.js +487 -0
- package/dist-tsc/index.d.ts +3 -1
- package/dist-tsc/index.d.ts.map +1 -1
- package/dist-tsc/index.js +1 -0
- package/dist-tsc/types.d.ts +255 -0
- package/dist-tsc/types.d.ts.map +1 -0
- package/dist-tsc/types.js +11 -0
- package/dist-tsc/use-brush.d.ts +53 -0
- package/dist-tsc/use-brush.d.ts.map +1 -0
- package/dist-tsc/use-brush.js +360 -0
- package/package.json +5 -3
- package/src/BrushOverlay.tsx +254 -0
- package/src/{Pluot.jsx → Pluot.tsx} +132 -46
- package/src/{Tooltip.jsx → Tooltip.tsx} +19 -3
- package/src/brush.test.ts +590 -0
- package/src/brush.ts +435 -0
- package/src/index.ts +24 -0
- package/src/types.ts +378 -0
- package/src/use-brush.ts +501 -0
- package/src/index.js +0 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { type RefObject } from "react";
|
|
2
|
+
import { type BrushEdge, type BrushGeometry } from "./brush.js";
|
|
3
|
+
import type { BrushState } from "./types.js";
|
|
4
|
+
import { type BrushPressProgress } from "./use-brush.js";
|
|
5
|
+
export type BrushOverlayProps = {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
/** From `useBrush`, so that presses on the handles below are not read as new brushes. */
|
|
9
|
+
overlayRef: RefObject<SVGSVGElement | null>;
|
|
10
|
+
/** Supplies the brushable region, which everything drawn here is clipped to. */
|
|
11
|
+
geometry: BrushGeometry;
|
|
12
|
+
/** Stroke color of the brush outline/handles; the fill uses the same color at reduced opacity. */
|
|
13
|
+
color: string;
|
|
14
|
+
brushState: BrushState | undefined;
|
|
15
|
+
pressProgress: BrushPressProgress | null;
|
|
16
|
+
/** Whether to draw the clear button (the pointer is over the brush and `enableBrushClear`). */
|
|
17
|
+
isBrushHovered: boolean;
|
|
18
|
+
enableBrushEdit: boolean;
|
|
19
|
+
onVertexMouseDown: (vertexIndex: number, event: React.MouseEvent) => void;
|
|
20
|
+
onEdgeMouseDown: (edge: BrushEdge, event: React.MouseEvent) => void;
|
|
21
|
+
onClearClick: (event: React.MouseEvent) => void;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Draws the brush as an SVG above the plot: a rectangle (or lasso polygon) with
|
|
25
|
+
* a circle at each vertex, plus the long-click progress wedge and the clear button.
|
|
26
|
+
*
|
|
27
|
+
* The SVG root is `pointerEvents: none` so that it never intercepts the camera's
|
|
28
|
+
* pan/zoom; only the vertex handles and the clear button opt back in.
|
|
29
|
+
*/
|
|
30
|
+
export declare function BrushOverlay(props: BrushOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=BrushOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrushOverlay.d.ts","sourceRoot":"","sources":["../src/BrushOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAkB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,EAEL,KAAK,SAAS,EAAE,KAAK,aAAa,EACnC,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAgBjF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,UAAU,EAAE,SAAS,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC5C,gFAAgF;IAChF,QAAQ,EAAE,aAAa,CAAC;IACxB,kGAAkG;IAClG,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACzC,+FAA+F;IAC/F,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC1E,eAAe,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACpE,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;CACjD,CAAC;AA0BF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CAqLpD"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useMemo } from "react";
|
|
3
|
+
import { describeWedgePath, getClearButtonCenter, getEdgeLine, getEditableEdges, getVerticesBoundingBox, } from "./brush.js";
|
|
4
|
+
import { CLEAR_BUTTON_RADIUS_PX } from "./use-brush.js";
|
|
5
|
+
const VERTEX_HANDLE_RADIUS_PX = 4;
|
|
6
|
+
const PRESS_INDICATOR_RADIUS_PX = 10;
|
|
7
|
+
/** How wide a side's invisible grab target is. Kept generous, since a side is 1.5px of ink. */
|
|
8
|
+
const EDGE_HANDLE_WIDTH_PX = 9;
|
|
9
|
+
/** Opacity of the brush's fill, relative to `color`; the stroke and handles stay fully opaque. */
|
|
10
|
+
const BRUSH_FILL_OPACITY = 0.15;
|
|
11
|
+
const HANDLE_FILL = "#ffffff";
|
|
12
|
+
const CLEAR_FILL = "#b34040";
|
|
13
|
+
/** A side is dragged along its perpendicular, so it takes the matching resize cursor. */
|
|
14
|
+
function getEdgeCursor(edge) {
|
|
15
|
+
return edge === "Left" || edge === "Right" ? "ew-resize" : "ns-resize";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The cursor for corner `vertexIndex`, which advertises the axes that corner can
|
|
19
|
+
* actually move: a range brush only resizes along the axis it selects, and a rect
|
|
20
|
+
* corner resizes along the diagonal it sits on.
|
|
21
|
+
*/
|
|
22
|
+
function getVertexCursor(shape, vertexIndex) {
|
|
23
|
+
if (shape === "RangeX") {
|
|
24
|
+
return "ew-resize";
|
|
25
|
+
}
|
|
26
|
+
if (shape === "RangeY") {
|
|
27
|
+
return "ns-resize";
|
|
28
|
+
}
|
|
29
|
+
if (shape === "Rect") {
|
|
30
|
+
// Corners are ordered clockwise from the top-left.
|
|
31
|
+
return vertexIndex % 2 === 0 ? "nwse-resize" : "nesw-resize";
|
|
32
|
+
}
|
|
33
|
+
return "grab";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Draws the brush as an SVG above the plot: a rectangle (or lasso polygon) with
|
|
37
|
+
* a circle at each vertex, plus the long-click progress wedge and the clear button.
|
|
38
|
+
*
|
|
39
|
+
* The SVG root is `pointerEvents: none` so that it never intercepts the camera's
|
|
40
|
+
* pan/zoom; only the vertex handles and the clear button opt back in.
|
|
41
|
+
*/
|
|
42
|
+
export function BrushOverlay(props) {
|
|
43
|
+
const { width, height, overlayRef, geometry, color, brushState, pressProgress, isBrushHovered, enableBrushEdit, onVertexMouseDown, onEdgeMouseDown, onClearClick, } = props;
|
|
44
|
+
const vertices = brushState?.vertices ?? [];
|
|
45
|
+
// Every shape but the lasso is a closed rectangle throughout the drag; a lasso
|
|
46
|
+
// is left open while the user is still drawing it, and closed once the drag completes.
|
|
47
|
+
const isClosed = (brushState !== undefined && brushState.shape !== "Polygon")
|
|
48
|
+
|| brushState?.status === "Complete";
|
|
49
|
+
const pathData = useMemo(() => {
|
|
50
|
+
if (vertices.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const points = vertices.map(v => `${v.x_pixels},${v.y_pixels}`).join(" L ");
|
|
54
|
+
return `M ${points}${isClosed ? " Z" : ""}`;
|
|
55
|
+
}, [vertices, isClosed]);
|
|
56
|
+
const clearButtonCenter = getClearButtonCenter(vertices, CLEAR_BUTTON_RADIUS_PX, geometry);
|
|
57
|
+
// `useId` emits colons, which are legal in an id but awkward inside `url(#...)`.
|
|
58
|
+
const clipPathId = `pluot-brush-clip-${useId().replace(/:/g, "")}`;
|
|
59
|
+
// While drawing a lasso, the intermediate vertices are too dense to be useful
|
|
60
|
+
// as handles, and they are not editable until the drag completes.
|
|
61
|
+
const shouldShowVertexHandles = isClosed;
|
|
62
|
+
// Sides are draggable only once the shape is settled, and only for the
|
|
63
|
+
// axis-aligned shapes; a lasso has no meaningful sides.
|
|
64
|
+
const editableEdges = enableBrushEdit && isClosed && brushState
|
|
65
|
+
? getEditableEdges(brushState.shape)
|
|
66
|
+
: [];
|
|
67
|
+
// The side handles are the only thing here that needs the extent, so it is not
|
|
68
|
+
// computed for a lasso or for a brush whose sides are not draggable.
|
|
69
|
+
const edgeBoundingBox = editableEdges.length > 0 ? getVerticesBoundingBox(vertices) : null;
|
|
70
|
+
return (_jsxs("svg", { ref: overlayRef, style: {
|
|
71
|
+
position: "absolute",
|
|
72
|
+
top: 0,
|
|
73
|
+
left: 0,
|
|
74
|
+
marginTop: 0,
|
|
75
|
+
marginLeft: 0,
|
|
76
|
+
marginRight: 0,
|
|
77
|
+
marginBottom: 0,
|
|
78
|
+
pointerEvents: "none",
|
|
79
|
+
// Sit above the canvas/SVG plot and the camera element.
|
|
80
|
+
zIndex: 1,
|
|
81
|
+
}, width: width, height: height, viewBox: `0 0 ${width} ${height}`, xmlns: "http://www.w3.org/2000/svg", children: [_jsx("defs", { children: _jsx("clipPath", { id: clipPathId, children: _jsx("rect", { x: geometry.brushLeft, y: geometry.brushTop, width: Math.max(geometry.brushRight - geometry.brushLeft, 0), height: Math.max(geometry.brushBottom - geometry.brushTop, 0) }) }) }), _jsxs("g", { clipPath: `url(#${clipPathId})`, children: [pathData ? (_jsx("path", { d: pathData, fill: isClosed ? color : "none", fillOpacity: isClosed ? BRUSH_FILL_OPACITY : undefined, stroke: color, strokeWidth: 1.5, strokeDasharray: brushState?.status === "Drawing" ? "4 3" : undefined })) : null, edgeBoundingBox === null ? null : editableEdges.map(edge => {
|
|
82
|
+
const [x1, y1, x2, y2] = getEdgeLine(edge, edgeBoundingBox);
|
|
83
|
+
return (_jsx("line", { x1: x1, y1: y1, x2: x2, y2: y2,
|
|
84
|
+
// Invisible ink, but a wide grab target.
|
|
85
|
+
stroke: "transparent", strokeWidth: EDGE_HANDLE_WIDTH_PX, strokeLinecap: "butt", style: { pointerEvents: "stroke", cursor: getEdgeCursor(edge) }, onMouseDown: event => onEdgeMouseDown(edge, event) }, edge));
|
|
86
|
+
}), shouldShowVertexHandles ? vertices.map((vertex, vertexIndex) => (_jsx("circle", { cx: vertex.x_pixels, cy: vertex.y_pixels, r: VERTEX_HANDLE_RADIUS_PX, fill: HANDLE_FILL, stroke: color, strokeWidth: 1.5, style: {
|
|
87
|
+
pointerEvents: enableBrushEdit ? "auto" : "none",
|
|
88
|
+
cursor: enableBrushEdit ? getVertexCursor(brushState?.shape, vertexIndex) : "default",
|
|
89
|
+
}, onMouseDown: enableBrushEdit ? (event => onVertexMouseDown(vertexIndex, event)) : undefined }, vertexIndex))) : null, isBrushHovered && clearButtonCenter ? (_jsxs("g", { style: { pointerEvents: "auto", cursor: "pointer" }, onClick: onClearClick, role: "button", "aria-label": "Clear brush", children: [_jsx("circle", { cx: clearButtonCenter[0], cy: clearButtonCenter[1], r: CLEAR_BUTTON_RADIUS_PX, fill: CLEAR_FILL }), _jsx("path", { d: `M ${clearButtonCenter[0] - 4} ${clearButtonCenter[1] - 4} L ${clearButtonCenter[0] + 4} ${clearButtonCenter[1] + 4} `
|
|
90
|
+
+ `M ${clearButtonCenter[0] + 4} ${clearButtonCenter[1] - 4} L ${clearButtonCenter[0] - 4} ${clearButtonCenter[1] + 4}`, stroke: "#ffffff", strokeWidth: 1.5, strokeLinecap: "round" })] })) : null, pressProgress ? (_jsxs("g", { children: [_jsx("circle", { cx: pressProgress.xPixels, cy: pressProgress.yPixels, r: PRESS_INDICATOR_RADIUS_PX, fill: "rgba(255, 255, 255, 0.6)", stroke: color, strokeWidth: 1.5 }), _jsx("path", { d: describeWedgePath(pressProgress.xPixels, pressProgress.yPixels, PRESS_INDICATOR_RADIUS_PX, pressProgress.fraction), fill: color })] })) : null] })] }));
|
|
91
|
+
}
|
package/dist-tsc/Pluot.d.ts
CHANGED
package/dist-tsc/Pluot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pluot.d.ts","sourceRoot":"","sources":["../src/Pluot.
|
|
1
|
+
{"version":3,"file":"Pluot.d.ts","sourceRoot":"","sources":["../src/Pluot.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACgB,UAAU,EACrC,MAAM,YAAY,CAAC;AA4CpB,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CA8lBtC"}
|
package/dist-tsc/Pluot.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import { useLayoutEffect, useEffect, useEffectEvent, useRef, useState, useMemo, useReducer, useId } from "react";
|
|
3
3
|
import lzs from "lz-string";
|
|
4
|
-
import {
|
|
5
|
-
import { initialize, getIsWasmReady, render_wasm, pick_wasm, normalizeStores, getStore,
|
|
4
|
+
import { throttle } from "lodash-es";
|
|
5
|
+
import { initialize, getIsWasmReady, render_wasm, pick_wasm, normalizeStores, getStore, checkWebGpuFeatureDetection, onMouseMove2d, onWheel2d, onMouseMove3d, onWheel3d, } from '@pluot/core';
|
|
6
6
|
import { Tooltip } from "./Tooltip.js";
|
|
7
|
+
import { BrushOverlay } from "./BrushOverlay.js";
|
|
8
|
+
import { useBrush } from "./use-brush.js";
|
|
7
9
|
// Needed due to "SyntaxError: Named export 'decompressFromUint8Array' not found.
|
|
8
10
|
// The requested module 'lz-string' is a CommonJS module,
|
|
9
11
|
// which may not support all module.exports as named exports."
|
|
@@ -25,24 +27,28 @@ const noop = () => { };
|
|
|
25
27
|
// Mouse movement (in pixels) beyond which a mousedown-to-click is
|
|
26
28
|
// considered a drag rather than a click, so that picking is skipped.
|
|
27
29
|
const DRAG_THRESHOLD_PX = 3;
|
|
30
|
+
// `pick_wasm` is typed `any` by wasm-bindgen, so `RawPickingResult` is what
|
|
31
|
+
// documents its wire format (see types.ts).
|
|
28
32
|
function normalizePickingResult(data) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
layer_id
|
|
33
|
+
return {
|
|
34
|
+
...data,
|
|
35
|
+
layer_results: data.layer_results.map(({ layer_id, info }) => ({
|
|
36
|
+
layer_id,
|
|
33
37
|
// This is needed because serde-wasm-bindgen
|
|
34
38
|
// converts Rust HashMap to JS Map.
|
|
35
|
-
info: Object.fromEntries(
|
|
36
|
-
}))
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
+
info: Object.fromEntries(info),
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
39
42
|
}
|
|
40
43
|
export function Pluot(props) {
|
|
41
44
|
const { schemaVersion = null, width: widthProp, height: heightProp, plotId, plotType, store: storeProp, storeName: storeNameProp, stores: storesProp, registerStores = true, plotParams, viewMode = "2d", marginBottom = 100.0, marginLeft = 100.0, marginTop = 100.0, marginRight = 100.0, aspectRatioMode = "Contain", // "Ignore", "Contain", "Cover"
|
|
42
45
|
aspectRatioAlignmentMode = "Start", // "Center", "Start", "End"
|
|
43
46
|
format = "Raster", // "Raster", "Vector"
|
|
44
|
-
minTimeout = 32, maxTimeout = 5000, allowSimultaneousRenders = true, debugMargins = false, backgroundColor = undefined, cameraMatrix: controlledCameraMatrix = null, setCameraMatrix: setControlledCameraMatrix = null, enableClick = false, enableTooltip = false, onClick: onClickProp = null, onHover: onHoverProp = null,
|
|
45
|
-
|
|
47
|
+
minTimeout = 32, maxTimeout = 5000, allowSimultaneousRenders = true, debugMargins = false, backgroundColor = undefined, cameraMatrix: controlledCameraMatrix = null, setCameraMatrix: setControlledCameraMatrix = null, enableClick = false, enableTooltip = false, onClick: onClickProp = null, onHover: onHoverProp = null, brushUnitsModeX = "Data", brushUnitsModeY = "Data", brushMarginTop, brushMarginRight, brushMarginBottom, brushMarginLeft, enableBrushCreate = false, enableBrushEdit = false, enableBrushClear = false, brushDelay = 1500, maybeBrushDelay = 250, persistBrush = false, brushMode = "Rect", brushColor = "#3b6ea5",
|
|
48
|
+
// An omitted `brush` means uncontrolled; a controlled parent signals the
|
|
49
|
+
// empty state with `NO_BRUSH`, never `undefined`.
|
|
50
|
+
brush = null, onBrush, onBrushEnd, onBrushClear, } = props;
|
|
51
|
+
const onClick = typeof onClickProp === 'function' ? onClickProp : noop;
|
|
46
52
|
const onHover = typeof onHoverProp === 'function' ? onHoverProp : identity;
|
|
47
53
|
// If cameraMatrix is not provided, then we manage the camera matrix internally.
|
|
48
54
|
const [uncontrolledCameraMatrix, setUncontrolledCameraMatrix] = useState(
|
|
@@ -84,6 +90,9 @@ export function Pluot(props) {
|
|
|
84
90
|
const svgRef = useRef(null);
|
|
85
91
|
const canvasRef = useRef(null);
|
|
86
92
|
const cameraElementRef = useRef(null);
|
|
93
|
+
// The outer (width x height) element, which is the coordinate space that both
|
|
94
|
+
// the brush overlay and the hover tooltip are positioned within.
|
|
95
|
+
const containerRef = useRef(null);
|
|
87
96
|
const tempButtonRef = useRef(null);
|
|
88
97
|
// We may want to update these things without triggering a re-render.
|
|
89
98
|
const isRenderingRef = useRef(false);
|
|
@@ -96,15 +105,25 @@ export function Pluot(props) {
|
|
|
96
105
|
// (Similar to the one used in the Vitessce heatmap)
|
|
97
106
|
// Reference: https://github.com/vitessce/vitessce/blob/71f17fb605768e0428fb15ed87b3ea34bcbb4803/packages/view-types/heatmap/src/Heatmap.js#L368
|
|
98
107
|
//const backlogRef = useRef([]);
|
|
99
|
-
const [backlogIteration, incBacklogIteration] = useReducer(i => i + 1, 0);
|
|
108
|
+
const [backlogIteration, incBacklogIteration] = useReducer((i) => i + 1, 0);
|
|
100
109
|
const [isWasmReady, setIsWasmReady] = useState(false);
|
|
101
110
|
const [didFirstRender, setDidFirstRender] = useState(false);
|
|
102
111
|
const [bailedEarly, setBailedEarly] = useState(true);
|
|
103
|
-
const [pickingResult, setPickingResult] = useState(null);
|
|
104
112
|
// hoverInfo.mouseX/mouseY are in the coordinate space of the outer
|
|
105
113
|
// (width x height) container, used to position the hover tooltip.
|
|
106
114
|
const [hoverInfo, setHoverInfo] = useState(null);
|
|
107
115
|
const progressBarId = useId();
|
|
116
|
+
const { brushState, overlayRef: brushOverlayRef, geometry: brushGeometry, pressProgress, isBrushHovered, isBrushingRef, shouldSuppressClickRef, onVertexMouseDown, onEdgeMouseDown, onClearClick, } = useBrush({
|
|
117
|
+
containerRef,
|
|
118
|
+
width, height,
|
|
119
|
+
marginTop, marginRight, marginBottom, marginLeft,
|
|
120
|
+
aspectRatioMode, aspectRatioAlignmentMode, cameraMatrix,
|
|
121
|
+
brushUnitsModeX, brushUnitsModeY,
|
|
122
|
+
brushMarginTop, brushMarginRight, brushMarginBottom, brushMarginLeft,
|
|
123
|
+
enableBrushCreate, enableBrushEdit, enableBrushClear,
|
|
124
|
+
brushDelay, maybeBrushDelay, persistBrush, brushMode,
|
|
125
|
+
brush, onBrush, onBrushEnd, onBrushClear,
|
|
126
|
+
});
|
|
108
127
|
useLayoutEffect(() => {
|
|
109
128
|
initialize().then(() => setIsWasmReady(getIsWasmReady()));
|
|
110
129
|
}, []);
|
|
@@ -125,6 +144,10 @@ export function Pluot(props) {
|
|
|
125
144
|
setCameraMatrix(nextCameraMatrix);
|
|
126
145
|
});
|
|
127
146
|
const mouseMoveHandler = useEffectEvent((event) => {
|
|
147
|
+
// A drag that is drawing or editing a brush must not also pan/rotate the camera.
|
|
148
|
+
if (isBrushingRef.current) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
128
151
|
const onMouseMove = viewMode === "3d" ? onMouseMove3d : onMouseMove2d;
|
|
129
152
|
const nextCameraMatrix = onMouseMove({
|
|
130
153
|
width,
|
|
@@ -179,7 +202,7 @@ export function Pluot(props) {
|
|
|
179
202
|
});
|
|
180
203
|
// The click-picking callback.
|
|
181
204
|
const pickFrame = useEffectEvent(async (screenCoordX, screenCoordY) => {
|
|
182
|
-
|
|
205
|
+
onClick(await pick(screenCoordX, screenCoordY));
|
|
183
206
|
});
|
|
184
207
|
// The hover-picking callback.
|
|
185
208
|
const hoverFrame = useEffectEvent(async (screenCoordX, screenCoordY) => {
|
|
@@ -210,6 +233,10 @@ export function Pluot(props) {
|
|
|
210
233
|
const mouseDownHandler = (event) => {
|
|
211
234
|
dragStartRef.current = { x: event.clientX, y: event.clientY };
|
|
212
235
|
didDragRef.current = false;
|
|
236
|
+
// A brush drag that ended outside the camera element never produced the
|
|
237
|
+
// click that would have consumed this flag, so clear it as the next
|
|
238
|
+
// interaction begins rather than letting it suppress that one too.
|
|
239
|
+
shouldSuppressClickRef.current = false;
|
|
213
240
|
};
|
|
214
241
|
const dragDetectHandler = (event) => {
|
|
215
242
|
if (!dragStartRef.current) {
|
|
@@ -226,16 +253,20 @@ export function Pluot(props) {
|
|
|
226
253
|
// Set up an onClick handler for picking.
|
|
227
254
|
const clickHandler = (event) => {
|
|
228
255
|
const wasDrag = didDragRef.current;
|
|
256
|
+
// A brush drag (or a click on the clear button) ends with a click on the
|
|
257
|
+
// camera element, which should not also run a picking query.
|
|
258
|
+
const wasBrush = shouldSuppressClickRef.current;
|
|
229
259
|
dragStartRef.current = null;
|
|
230
260
|
didDragRef.current = false;
|
|
231
|
-
|
|
261
|
+
shouldSuppressClickRef.current = false;
|
|
262
|
+
if (enableClick && !wasDrag && !wasBrush) {
|
|
232
263
|
pickFrame(event.offsetX, event.offsetY);
|
|
233
264
|
}
|
|
234
265
|
};
|
|
235
266
|
cameraEl.addEventListener("click", clickHandler);
|
|
236
267
|
// Set up hover handlers for picking, only when the onHover prop is provided.
|
|
237
268
|
const hoverMoveHandler = (event) => {
|
|
238
|
-
if (enableTooltip) {
|
|
269
|
+
if (enableTooltip && !isBrushingRef.current) {
|
|
239
270
|
throttledHoverFrame(event.offsetX, event.offsetY);
|
|
240
271
|
}
|
|
241
272
|
};
|
|
@@ -338,9 +369,9 @@ export function Pluot(props) {
|
|
|
338
369
|
currentTimeout.current = minTimeout;
|
|
339
370
|
setBailedEarly(false); // Update this to hide the loading indicator.
|
|
340
371
|
// Clear the LRU cache for the store (via its store_name) corresponding to the rendered plot.
|
|
341
|
-
Object.keys(stores).forEach(storeName => {
|
|
372
|
+
Object.keys(stores ?? {}).forEach(storeName => {
|
|
342
373
|
const storeUsed = getStore(storeName);
|
|
343
|
-
if (storeUsed &&
|
|
374
|
+
if (storeUsed && typeof storeUsed.clearCache === 'function') {
|
|
344
375
|
storeUsed.clearCache();
|
|
345
376
|
}
|
|
346
377
|
});
|
|
@@ -396,11 +427,17 @@ export function Pluot(props) {
|
|
|
396
427
|
return {
|
|
397
428
|
position: "absolute",
|
|
398
429
|
pointerEvents: "none",
|
|
430
|
+
// Above the brush overlay, so a persisted brush does not tint the tooltip.
|
|
431
|
+
zIndex: 2,
|
|
399
432
|
...(isTop ? { top: mouseY + offsetPx } : { bottom: height - mouseY + offsetPx + extraPx }),
|
|
400
433
|
...(isLeft ? { left: mouseX + offsetPx + extraPx } : { right: width - mouseX + offsetPx }),
|
|
401
434
|
};
|
|
402
435
|
}, [hoverInfo, width, height]);
|
|
403
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", {
|
|
436
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: containerRef, style: {
|
|
437
|
+
width, height, position: "relative", backgroundColor,
|
|
438
|
+
// Long-clicking to start a brush otherwise selects surrounding text.
|
|
439
|
+
userSelect: enableBrushCreate ? "none" : undefined,
|
|
440
|
+
}, children: [!supportsWebGpu ? (_jsx("p", { children: supportsWebGpuMessage })) : null, _jsx("div", { ref: cameraElementRef, style: {
|
|
404
441
|
position: "absolute",
|
|
405
442
|
top: marginTop,
|
|
406
443
|
left: marginLeft,
|
|
@@ -418,5 +455,5 @@ export function Pluot(props) {
|
|
|
418
455
|
}) : {}) })) : (_jsx("canvas", { ref: canvasRef, style: { width, height, border: `${debugMargins ? 1 : 0}px solid black` }, width: width, height: height, ...(bailedEarly ? ({
|
|
419
456
|
['aria-busy']: true,
|
|
420
457
|
['aria-describedby']: progressBarId,
|
|
421
|
-
}) : {}) })), hoverInfo ? (_jsx("div", { style: hoverStyle, children: _jsx(Tooltip, { content: hoverInfo.content, asTable: true }) })) : null] }), _jsx("button", { ref: tempButtonRef, style: { display: 'none' }, children: "Try lookAt" })] }));
|
|
458
|
+
}) : {}) })), _jsx(BrushOverlay, { width: width, height: height, overlayRef: brushOverlayRef, geometry: brushGeometry, color: brushColor, brushState: brushState, pressProgress: pressProgress, isBrushHovered: isBrushHovered, enableBrushEdit: enableBrushEdit, onVertexMouseDown: onVertexMouseDown, onEdgeMouseDown: onEdgeMouseDown, onClearClick: onClearClick }), hoverInfo ? (_jsx("div", { style: hoverStyle ?? undefined, children: _jsx(Tooltip, { content: hoverInfo.content, asTable: true }) })) : null] }), _jsx("button", { ref: tempButtonRef, style: { display: 'none' }, children: "Try lookAt" })] }));
|
|
422
459
|
}
|
package/dist-tsc/Tooltip.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../src/Tooltip.
|
|
1
|
+
{"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../src/Tooltip.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAoB/C,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,kDA6B1C"}
|
package/dist-tsc/Tooltip.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
const boxShadow = '5px 5px 15px rgb(0 0 0 / 20%)';
|
|
4
|
+
// Table cell values come from arbitrary onHover return objects, so narrow
|
|
5
|
+
// them to something React can actually render.
|
|
6
|
+
function renderCell(value) {
|
|
7
|
+
if (value === null || value === undefined || typeof value === "boolean") {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
if (React.isValidElement(value)) {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return JSON.stringify(value);
|
|
17
|
+
}
|
|
4
18
|
export function Tooltip(props) {
|
|
5
19
|
const { content, asTable = false, } = props;
|
|
6
20
|
if (!content) {
|
|
@@ -13,7 +27,7 @@ export function Tooltip(props) {
|
|
|
13
27
|
return content;
|
|
14
28
|
}
|
|
15
29
|
if (asTable) {
|
|
16
|
-
return (_jsx("table", { style: { display: 'inline-block', marginBottom: 0, opacity: 0.9, padding: '5px', backgroundColor: 'white', borderRadius: '2px', boxShadow }, children: _jsx("tbody", { children: Object.entries(content).map(([key, value]) => (_jsxs("tr", { children: [_jsx("th", { style: { border: 'none', fontSize: '12px', outline: 0, padding: '0 2px', textAlign: 'left' }, children: key }), _jsx("td", { style: { border: 'none', fontSize: '12px', outline: 0, padding: '0 2px', textAlign: 'left' }, children: value })] }, key))) }) }));
|
|
30
|
+
return (_jsx("table", { style: { display: 'inline-block', marginBottom: 0, opacity: 0.9, padding: '5px', backgroundColor: 'white', borderRadius: '2px', boxShadow }, children: _jsx("tbody", { children: Object.entries(content).map(([key, value]) => (_jsxs("tr", { children: [_jsx("th", { style: { border: 'none', fontSize: '12px', outline: 0, padding: '0 2px', textAlign: 'left' }, children: key }), _jsx("td", { style: { border: 'none', fontSize: '12px', outline: 0, padding: '0 2px', textAlign: 'left' }, children: renderCell(value) })] }, key))) }) }));
|
|
17
31
|
}
|
|
18
32
|
return _jsx("pre", { children: JSON.stringify(content, null, 2) });
|
|
19
33
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { type AspectRatioMode, type AspectRatioAlignmentMode, type Bounds, type CameraMatrix } from "@pluot/core";
|
|
2
|
+
import type { BrushMode, BrushState, BrushUnitsMode, BrushVertex, RectLikeBrushMode } from "./types.js";
|
|
3
|
+
/** An axis-aligned brush extent, in container pixels. */
|
|
4
|
+
export type BrushBoundingBox = {
|
|
5
|
+
left: number;
|
|
6
|
+
top: number;
|
|
7
|
+
right: number;
|
|
8
|
+
bottom: number;
|
|
9
|
+
};
|
|
10
|
+
/** One side of an axis-aligned brush, which the user can drag to extend it. */
|
|
11
|
+
export type BrushEdge = "Top" | "Right" | "Bottom" | "Left";
|
|
12
|
+
/**
|
|
13
|
+
* Everything needed to convert a brush vertex between the three units modes.
|
|
14
|
+
*
|
|
15
|
+
* There are two rectangles involved, both expressed in *container* pixels
|
|
16
|
+
* (relative to the top-left of the outer `width` x `height` element, Y down),
|
|
17
|
+
* which is also the coordinate space of the brush overlay SVG:
|
|
18
|
+
*
|
|
19
|
+
* - The **layer** rect (the camera region, inside `margin*`), which anchors the
|
|
20
|
+
* `Data` units mode, since that is the region the camera matrix maps onto.
|
|
21
|
+
* - The **brushable** rect (inside `brushMargin*`), which bounds where the user
|
|
22
|
+
* may draw and which anchors the `Normalized` units mode.
|
|
23
|
+
*
|
|
24
|
+
* Note that `Data` and `Normalized` are Y-up (matching Pluot's data coordinate
|
|
25
|
+
* system, where `getBounds().yMin` is the bottom of the layer), whereas
|
|
26
|
+
* `Pixels` is Y-down (matching the DOM/SVG convention).
|
|
27
|
+
*/
|
|
28
|
+
export type BrushGeometry = {
|
|
29
|
+
layerLeft: number;
|
|
30
|
+
layerTop: number;
|
|
31
|
+
layerWidth: number;
|
|
32
|
+
layerHeight: number;
|
|
33
|
+
brushLeft: number;
|
|
34
|
+
brushTop: number;
|
|
35
|
+
brushRight: number;
|
|
36
|
+
brushBottom: number;
|
|
37
|
+
/** The visible data range of the layer rect, under the current camera. */
|
|
38
|
+
dataBounds: Required<Bounds>;
|
|
39
|
+
};
|
|
40
|
+
export type BrushGeometryParams = {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
marginTop: number;
|
|
44
|
+
marginRight: number;
|
|
45
|
+
marginBottom: number;
|
|
46
|
+
marginLeft: number;
|
|
47
|
+
/** Each defaults to the corresponding layer margin when undefined. */
|
|
48
|
+
brushMarginTop: number | undefined;
|
|
49
|
+
brushMarginRight: number | undefined;
|
|
50
|
+
brushMarginBottom: number | undefined;
|
|
51
|
+
brushMarginLeft: number | undefined;
|
|
52
|
+
brushUnitsModeX: BrushUnitsMode;
|
|
53
|
+
brushUnitsModeY: BrushUnitsMode;
|
|
54
|
+
aspectRatioMode: AspectRatioMode;
|
|
55
|
+
aspectRatioAlignmentMode: AspectRatioAlignmentMode;
|
|
56
|
+
cameraMatrix: CameraMatrix;
|
|
57
|
+
};
|
|
58
|
+
export declare function getBrushGeometry(params: BrushGeometryParams): BrushGeometry;
|
|
59
|
+
/**
|
|
60
|
+
* Build a full {@link BrushVertex} (all three units modes) from a position in
|
|
61
|
+
* container pixels.
|
|
62
|
+
*/
|
|
63
|
+
export declare function vertexFromPixels(xPixels: number, yPixels: number, geom: BrushGeometry): BrushVertex;
|
|
64
|
+
/**
|
|
65
|
+
* Recover the container-pixel position of a vertex from whichever of its
|
|
66
|
+
* representations is authoritative for each axis.
|
|
67
|
+
*
|
|
68
|
+
* Only the representation matching the units mode survives a camera or resize
|
|
69
|
+
* change; the other two are derived, so they must be recomputed rather than
|
|
70
|
+
* read back (see {@link reprojectVertex}).
|
|
71
|
+
*/
|
|
72
|
+
export declare function pixelsFromVertex(vertex: BrushVertex, geom: BrushGeometry, brushUnitsModeX: BrushUnitsMode, brushUnitsModeY: BrushUnitsMode): [number, number];
|
|
73
|
+
/**
|
|
74
|
+
* Re-derive the non-authoritative representations of a vertex under the current
|
|
75
|
+
* geometry. This is what makes a `Data`-units brush track the camera as the user
|
|
76
|
+
* zooms/pans: `x_data`/`y_data` stay fixed while the pixel positions move.
|
|
77
|
+
*/
|
|
78
|
+
export declare function reprojectVertex(vertex: BrushVertex, geom: BrushGeometry, brushUnitsModeX: BrushUnitsMode, brushUnitsModeY: BrushUnitsMode): BrushVertex;
|
|
79
|
+
export declare function reprojectBrushState(state: BrushState, geom: BrushGeometry, brushUnitsModeX: BrushUnitsMode, brushUnitsModeY: BrushUnitsMode): BrushState;
|
|
80
|
+
/** Restrict a container-pixel position to the brushable region. */
|
|
81
|
+
export declare function clampToBrushRegion(xPixels: number, yPixels: number, geom: BrushGeometry): [number, number];
|
|
82
|
+
/**
|
|
83
|
+
* The four corners of the rect spanned by two opposite corners, ordered
|
|
84
|
+
* clockwise in pixel space starting from the top-left, so that corner `i` is
|
|
85
|
+
* always diagonally opposite corner `(i + 2) % 4`.
|
|
86
|
+
*
|
|
87
|
+
* `RangeX` and `RangeY` select along a single axis, so the other axis is
|
|
88
|
+
* discarded and pinned to the full extent of the brushable region.
|
|
89
|
+
*/
|
|
90
|
+
export declare function rectVerticesFromCorners(x0: number, y0: number, x1: number, y1: number, geom: BrushGeometry, shape?: RectLikeBrushMode): BrushVertex[];
|
|
91
|
+
/** The bounding box, in container pixels, of a list of already-reprojected vertices. */
|
|
92
|
+
export declare function getVerticesBoundingBox(vertices: BrushVertex[]): BrushBoundingBox | null;
|
|
93
|
+
/**
|
|
94
|
+
* Whether a brush is too small to be a selection.
|
|
95
|
+
*
|
|
96
|
+
* A long-click that never turns into a drag produces a rect whose four corners
|
|
97
|
+
* coincide, which draws as a stray dot rather than as nothing, so these states
|
|
98
|
+
* are held back instead of being committed.
|
|
99
|
+
*/
|
|
100
|
+
export declare function isDegenerateBrush(state: BrushState): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Where the clear button sits: adjacent to the brush's first vertex — the
|
|
103
|
+
* top-left corner of a rect, or the point a lasso was started from.
|
|
104
|
+
*
|
|
105
|
+
* Anchoring to the first vertex keeps the button in one place while a lasso is
|
|
106
|
+
* being drawn, rather than trailing the cursor around the shape. It is pushed
|
|
107
|
+
* outwards along the ray from the centroid through that vertex, so it lands
|
|
108
|
+
* outside the brush and does not obscure the brushed content. Returns `null` for
|
|
109
|
+
* an empty brush.
|
|
110
|
+
*
|
|
111
|
+
* The result is kept within the brushable region, since the overlay is clipped to
|
|
112
|
+
* that region and a button pushed outside it would be invisible and unclickable.
|
|
113
|
+
*/
|
|
114
|
+
export declare function getClearButtonCenter(vertices: BrushVertex[], radius: number, geom: BrushGeometry): [number, number] | null;
|
|
115
|
+
/**
|
|
116
|
+
* Which sides of a brush the user may drag to extend it.
|
|
117
|
+
*
|
|
118
|
+
* A range brush pins its unselected axis to the whole brushable region, so
|
|
119
|
+
* dragging those two sides could not change anything and they are left out.
|
|
120
|
+
*/
|
|
121
|
+
export declare function getEditableEdges(shape: BrushMode): BrushEdge[];
|
|
122
|
+
/** The endpoints `[x1, y1, x2, y2]` of an edge, in container pixels. */
|
|
123
|
+
export declare function getEdgeLine(edge: BrushEdge, boundingBox: BrushBoundingBox): [number, number, number, number];
|
|
124
|
+
/**
|
|
125
|
+
* The two opposite corners that dragging `edge` spans: the corner that stays
|
|
126
|
+
* put, and the corner that follows the cursor along `axis` only.
|
|
127
|
+
*
|
|
128
|
+
* Expressing an edge drag as a pair of corners lets it reuse
|
|
129
|
+
* {@link rectVerticesFromCorners}, which also means dragging a side past its
|
|
130
|
+
* opposite side flips the brush rather than inverting it.
|
|
131
|
+
*/
|
|
132
|
+
export declare function getEdgeDragCorners(edge: BrushEdge, boundingBox: BrushBoundingBox): {
|
|
133
|
+
axis: "X" | "Y";
|
|
134
|
+
fixedX: number;
|
|
135
|
+
fixedY: number;
|
|
136
|
+
movingX: number;
|
|
137
|
+
movingY: number;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Whether a container-pixel position lies inside a brush, used to decide when to
|
|
141
|
+
* reveal the clear button. Hit-testing is done here rather than with SVG pointer
|
|
142
|
+
* events so that the overlay never swallows camera pan/zoom interactions.
|
|
143
|
+
* TODO: replace this by using the regular onHover events in the brush overlay SVG.
|
|
144
|
+
* The challenge with using the regular onHover events in the overlay is that
|
|
145
|
+
* it makes it tricky to avoid absorbing the hover/mouse events which the camera pan/zoom need.
|
|
146
|
+
* An alternative/intermediate optimization would be to compute the polygon bounding box
|
|
147
|
+
* upon the polygon creation/modification, and do hit-testing against that cached bounding box instead.
|
|
148
|
+
*/
|
|
149
|
+
export declare function isPointInBrush(xPixels: number, yPixels: number, vertices: BrushVertex[]): boolean;
|
|
150
|
+
/**
|
|
151
|
+
* An SVG path for a pie wedge filled clockwise from 12 o'clock, used to
|
|
152
|
+
* visualize progress towards the long-click that starts a brush.
|
|
153
|
+
*/
|
|
154
|
+
export declare function describeWedgePath(cx: number, cy: number, radius: number, fraction: number): string;
|
|
155
|
+
//# sourceMappingURL=brush.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brush.d.ts","sourceRoot":"","sources":["../src/brush.ts"],"names":[],"mappings":"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"}
|