@pluot/react 0.1.16 → 0.1.18
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 +1066 -110
- package/dist-tsc/BrushOverlay.d.ts +31 -0
- package/dist-tsc/BrushOverlay.d.ts.map +1 -0
- package/dist-tsc/BrushOverlay.js +95 -0
- package/dist-tsc/Pluot.d.ts +2 -1
- package/dist-tsc/Pluot.d.ts.map +1 -1
- package/dist-tsc/Pluot.js +118 -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 +283 -0
- package/dist-tsc/types.d.ts.map +1 -0
- package/dist-tsc/types.js +11 -0
- package/dist-tsc/use-brush.d.ts +55 -0
- package/dist-tsc/use-brush.d.ts.map +1 -0
- package/dist-tsc/use-brush.js +361 -0
- package/package.json +5 -3
- package/src/BrushOverlay.tsx +258 -0
- package/src/{Pluot.jsx → Pluot.tsx} +200 -47
- 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 +26 -0
- package/src/types.ts +412 -0
- package/src/use-brush.ts +505 -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,2CAyLpD"}
|
|
@@ -0,0 +1,95 @@
|
|
|
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. RangeX/RangeY
|
|
61
|
+
// brushes only resize along one axis via their edges, so their corners are not
|
|
62
|
+
// meaningful drag handles either.
|
|
63
|
+
const shouldShowVertexHandles = isClosed
|
|
64
|
+
&& brushState?.shape !== "RangeX"
|
|
65
|
+
&& brushState?.shape !== "RangeY";
|
|
66
|
+
// Sides are draggable only once the shape is settled, and only for the
|
|
67
|
+
// axis-aligned shapes; a lasso has no meaningful sides.
|
|
68
|
+
const editableEdges = enableBrushEdit && isClosed && brushState
|
|
69
|
+
? getEditableEdges(brushState.shape)
|
|
70
|
+
: [];
|
|
71
|
+
// The side handles are the only thing here that needs the extent, so it is not
|
|
72
|
+
// computed for a lasso or for a brush whose sides are not draggable.
|
|
73
|
+
const edgeBoundingBox = editableEdges.length > 0 ? getVerticesBoundingBox(vertices) : null;
|
|
74
|
+
return (_jsxs("svg", { ref: overlayRef, style: {
|
|
75
|
+
position: "absolute",
|
|
76
|
+
top: 0,
|
|
77
|
+
left: 0,
|
|
78
|
+
marginTop: 0,
|
|
79
|
+
marginLeft: 0,
|
|
80
|
+
marginRight: 0,
|
|
81
|
+
marginBottom: 0,
|
|
82
|
+
pointerEvents: "none",
|
|
83
|
+
// Sit above the canvas/SVG plot and the camera element.
|
|
84
|
+
zIndex: 1,
|
|
85
|
+
}, 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 => {
|
|
86
|
+
const [x1, y1, x2, y2] = getEdgeLine(edge, edgeBoundingBox);
|
|
87
|
+
return (_jsx("line", { x1: x1, y1: y1, x2: x2, y2: y2,
|
|
88
|
+
// Invisible ink, but a wide grab target.
|
|
89
|
+
stroke: "transparent", strokeWidth: EDGE_HANDLE_WIDTH_PX, strokeLinecap: "butt", style: { pointerEvents: "stroke", cursor: getEdgeCursor(edge) }, onMouseDown: event => onEdgeMouseDown(edge, event) }, edge));
|
|
90
|
+
}), 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: {
|
|
91
|
+
pointerEvents: enableBrushEdit ? "auto" : "none",
|
|
92
|
+
cursor: enableBrushEdit ? getVertexCursor(brushState?.shape, vertexIndex) : "default",
|
|
93
|
+
}, 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} `
|
|
94
|
+
+ `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] })] }));
|
|
95
|
+
}
|
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,EAC4C,UAAU,EAEjE,MAAM,YAAY,CAAC;AA2DpB,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CAipBtC"}
|
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, brush_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,44 @@ 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
|
-
|
|
39
|
+
info: Object.fromEntries(info),
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// `brush_wasm` is typed `any` by wasm-bindgen, so `RawBrushingResult` is what
|
|
44
|
+
// documents its wire format (see types.ts).
|
|
45
|
+
function normalizeBrushingResult(data) {
|
|
46
|
+
return {
|
|
47
|
+
...data,
|
|
48
|
+
layer_results: data.layer_results.map(({ layer_id, info, element_info }) => ({
|
|
49
|
+
layer_id,
|
|
50
|
+
// This is needed because serde-wasm-bindgen
|
|
51
|
+
// converts Rust HashMap to JS Map.
|
|
52
|
+
info: Object.fromEntries(info),
|
|
53
|
+
element_info: Object.fromEntries(element_info),
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
39
56
|
}
|
|
40
57
|
export function Pluot(props) {
|
|
41
58
|
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
59
|
aspectRatioAlignmentMode = "Start", // "Center", "Start", "End"
|
|
43
60
|
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
|
-
|
|
61
|
+
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",
|
|
62
|
+
// An omitted `brush` means uncontrolled; a controlled parent signals the
|
|
63
|
+
// empty state with `NO_BRUSH`, never `undefined`.
|
|
64
|
+
brush = null, onBrush, onBrushEnd, onBrushClear,
|
|
65
|
+
// Temporary workaround. See comments in LruStore.clearCache.
|
|
66
|
+
shouldClearCache = true, } = props;
|
|
67
|
+
const onClick = typeof onClickProp === 'function' ? onClickProp : noop;
|
|
46
68
|
const onHover = typeof onHoverProp === 'function' ? onHoverProp : identity;
|
|
47
69
|
// If cameraMatrix is not provided, then we manage the camera matrix internally.
|
|
48
70
|
const [uncontrolledCameraMatrix, setUncontrolledCameraMatrix] = useState(
|
|
@@ -84,6 +106,9 @@ export function Pluot(props) {
|
|
|
84
106
|
const svgRef = useRef(null);
|
|
85
107
|
const canvasRef = useRef(null);
|
|
86
108
|
const cameraElementRef = useRef(null);
|
|
109
|
+
// The outer (width x height) element, which is the coordinate space that both
|
|
110
|
+
// the brush overlay and the hover tooltip are positioned within.
|
|
111
|
+
const containerRef = useRef(null);
|
|
87
112
|
const tempButtonRef = useRef(null);
|
|
88
113
|
// We may want to update these things without triggering a re-render.
|
|
89
114
|
const isRenderingRef = useRef(false);
|
|
@@ -96,15 +121,68 @@ export function Pluot(props) {
|
|
|
96
121
|
// (Similar to the one used in the Vitessce heatmap)
|
|
97
122
|
// Reference: https://github.com/vitessce/vitessce/blob/71f17fb605768e0428fb15ed87b3ea34bcbb4803/packages/view-types/heatmap/src/Heatmap.js#L368
|
|
98
123
|
//const backlogRef = useRef([]);
|
|
99
|
-
const [backlogIteration, incBacklogIteration] = useReducer(i => i + 1, 0);
|
|
124
|
+
const [backlogIteration, incBacklogIteration] = useReducer((i) => i + 1, 0);
|
|
100
125
|
const [isWasmReady, setIsWasmReady] = useState(false);
|
|
101
126
|
const [didFirstRender, setDidFirstRender] = useState(false);
|
|
102
127
|
const [bailedEarly, setBailedEarly] = useState(true);
|
|
103
|
-
const [pickingResult, setPickingResult] = useState(null);
|
|
104
128
|
// hoverInfo.mouseX/mouseY are in the coordinate space of the outer
|
|
105
129
|
// (width x height) container, used to position the hover tooltip.
|
|
106
130
|
const [hoverInfo, setHoverInfo] = useState(null);
|
|
107
131
|
const progressBarId = useId();
|
|
132
|
+
// Runs the brush query against the wasm module for a given brush state,
|
|
133
|
+
// analogous to `pick` below (defined here, ahead of `pick`, since `useBrush`
|
|
134
|
+
// needs it immediately).
|
|
135
|
+
const runBrush = useEffectEvent(async (state) => {
|
|
136
|
+
if (!isWasmReady) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const renderParams = {
|
|
140
|
+
schema_version: schemaVersion,
|
|
141
|
+
width,
|
|
142
|
+
height,
|
|
143
|
+
format: format,
|
|
144
|
+
margin_bottom: marginBottom,
|
|
145
|
+
margin_left: marginLeft,
|
|
146
|
+
margin_top: marginTop,
|
|
147
|
+
margin_right: marginRight,
|
|
148
|
+
device_pixel_ratio: window.devicePixelRatio,
|
|
149
|
+
aspect_ratio_mode: aspectRatioMode,
|
|
150
|
+
aspect_ratio_alignment_mode: aspectRatioAlignmentMode,
|
|
151
|
+
view_mode: viewMode,
|
|
152
|
+
pickable: false,
|
|
153
|
+
camera_view: cameraMatrix,
|
|
154
|
+
plot_id: plotId,
|
|
155
|
+
plot_type: plotType,
|
|
156
|
+
stores,
|
|
157
|
+
plot_params: plotParams,
|
|
158
|
+
timeout: currentTimeout.current,
|
|
159
|
+
wait_for_store_gets: false,
|
|
160
|
+
cache_enabled: true,
|
|
161
|
+
svg_compression_enabled: true,
|
|
162
|
+
svg_include_document: false,
|
|
163
|
+
};
|
|
164
|
+
// Brush vertices are container-relative pixels with Y increasing downwards;
|
|
165
|
+
// the wasm side expects screen coordinates with Y increasing upwards (as
|
|
166
|
+
// with the `screenCoordX`/`screenCoordY` passed to `pick_wasm` below).
|
|
167
|
+
const brushParams = {
|
|
168
|
+
screen_vertices: state.vertices.map((vertex) => ({ x: vertex.x_pixels, y: height - vertex.y_pixels })),
|
|
169
|
+
brush_units_mode_x: brushUnitsModeX,
|
|
170
|
+
brush_units_mode_y: brushUnitsModeY,
|
|
171
|
+
brush_mode: state.shape,
|
|
172
|
+
};
|
|
173
|
+
return normalizeBrushingResult(await brush_wasm(renderParams, brushParams));
|
|
174
|
+
});
|
|
175
|
+
const { brushState, overlayRef: brushOverlayRef, geometry: brushGeometry, pressProgress, isBrushHovered, isBrushingRef, shouldSuppressClickRef, onVertexMouseDown, onEdgeMouseDown, onClearClick, } = useBrush({
|
|
176
|
+
containerRef,
|
|
177
|
+
width, height,
|
|
178
|
+
marginTop, marginRight, marginBottom, marginLeft,
|
|
179
|
+
aspectRatioMode, aspectRatioAlignmentMode, cameraMatrix,
|
|
180
|
+
brushUnitsModeX, brushUnitsModeY,
|
|
181
|
+
brushMarginTop, brushMarginRight, brushMarginBottom, brushMarginLeft,
|
|
182
|
+
enableBrushCreate, enableBrushEdit, enableBrushClear,
|
|
183
|
+
brushDelay, maybeBrushDelay, persistBrush, brushMode,
|
|
184
|
+
brush, onBrush, onBrushEnd, onBrushClear, runBrush,
|
|
185
|
+
});
|
|
108
186
|
useLayoutEffect(() => {
|
|
109
187
|
initialize().then(() => setIsWasmReady(getIsWasmReady()));
|
|
110
188
|
}, []);
|
|
@@ -125,6 +203,10 @@ export function Pluot(props) {
|
|
|
125
203
|
setCameraMatrix(nextCameraMatrix);
|
|
126
204
|
});
|
|
127
205
|
const mouseMoveHandler = useEffectEvent((event) => {
|
|
206
|
+
// A drag that is drawing or editing a brush must not also pan/rotate the camera.
|
|
207
|
+
if (isBrushingRef.current) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
128
210
|
const onMouseMove = viewMode === "3d" ? onMouseMove3d : onMouseMove2d;
|
|
129
211
|
const nextCameraMatrix = onMouseMove({
|
|
130
212
|
width,
|
|
@@ -179,7 +261,7 @@ export function Pluot(props) {
|
|
|
179
261
|
});
|
|
180
262
|
// The click-picking callback.
|
|
181
263
|
const pickFrame = useEffectEvent(async (screenCoordX, screenCoordY) => {
|
|
182
|
-
|
|
264
|
+
onClick(await pick(screenCoordX, screenCoordY));
|
|
183
265
|
});
|
|
184
266
|
// The hover-picking callback.
|
|
185
267
|
const hoverFrame = useEffectEvent(async (screenCoordX, screenCoordY) => {
|
|
@@ -210,6 +292,10 @@ export function Pluot(props) {
|
|
|
210
292
|
const mouseDownHandler = (event) => {
|
|
211
293
|
dragStartRef.current = { x: event.clientX, y: event.clientY };
|
|
212
294
|
didDragRef.current = false;
|
|
295
|
+
// A brush drag that ended outside the camera element never produced the
|
|
296
|
+
// click that would have consumed this flag, so clear it as the next
|
|
297
|
+
// interaction begins rather than letting it suppress that one too.
|
|
298
|
+
shouldSuppressClickRef.current = false;
|
|
213
299
|
};
|
|
214
300
|
const dragDetectHandler = (event) => {
|
|
215
301
|
if (!dragStartRef.current) {
|
|
@@ -226,16 +312,20 @@ export function Pluot(props) {
|
|
|
226
312
|
// Set up an onClick handler for picking.
|
|
227
313
|
const clickHandler = (event) => {
|
|
228
314
|
const wasDrag = didDragRef.current;
|
|
315
|
+
// A brush drag (or a click on the clear button) ends with a click on the
|
|
316
|
+
// camera element, which should not also run a picking query.
|
|
317
|
+
const wasBrush = shouldSuppressClickRef.current;
|
|
229
318
|
dragStartRef.current = null;
|
|
230
319
|
didDragRef.current = false;
|
|
231
|
-
|
|
320
|
+
shouldSuppressClickRef.current = false;
|
|
321
|
+
if (enableClick && !wasDrag && !wasBrush) {
|
|
232
322
|
pickFrame(event.offsetX, event.offsetY);
|
|
233
323
|
}
|
|
234
324
|
};
|
|
235
325
|
cameraEl.addEventListener("click", clickHandler);
|
|
236
326
|
// Set up hover handlers for picking, only when the onHover prop is provided.
|
|
237
327
|
const hoverMoveHandler = (event) => {
|
|
238
|
-
if (enableTooltip) {
|
|
328
|
+
if (enableTooltip && !isBrushingRef.current) {
|
|
239
329
|
throttledHoverFrame(event.offsetX, event.offsetY);
|
|
240
330
|
}
|
|
241
331
|
};
|
|
@@ -338,9 +428,9 @@ export function Pluot(props) {
|
|
|
338
428
|
currentTimeout.current = minTimeout;
|
|
339
429
|
setBailedEarly(false); // Update this to hide the loading indicator.
|
|
340
430
|
// Clear the LRU cache for the store (via its store_name) corresponding to the rendered plot.
|
|
341
|
-
Object.keys(stores).forEach(storeName => {
|
|
431
|
+
Object.keys(stores ?? {}).forEach(storeName => {
|
|
342
432
|
const storeUsed = getStore(storeName);
|
|
343
|
-
if (storeUsed &&
|
|
433
|
+
if (storeUsed && typeof storeUsed.clearCache === 'function' && shouldClearCache) {
|
|
344
434
|
storeUsed.clearCache();
|
|
345
435
|
}
|
|
346
436
|
});
|
|
@@ -396,11 +486,17 @@ export function Pluot(props) {
|
|
|
396
486
|
return {
|
|
397
487
|
position: "absolute",
|
|
398
488
|
pointerEvents: "none",
|
|
489
|
+
// Above the brush overlay, so a persisted brush does not tint the tooltip.
|
|
490
|
+
zIndex: 2,
|
|
399
491
|
...(isTop ? { top: mouseY + offsetPx } : { bottom: height - mouseY + offsetPx + extraPx }),
|
|
400
492
|
...(isLeft ? { left: mouseX + offsetPx + extraPx } : { right: width - mouseX + offsetPx }),
|
|
401
493
|
};
|
|
402
494
|
}, [hoverInfo, width, height]);
|
|
403
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", {
|
|
495
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: containerRef, style: {
|
|
496
|
+
width, height, position: "relative", backgroundColor,
|
|
497
|
+
// Long-clicking to start a brush otherwise selects surrounding text.
|
|
498
|
+
userSelect: enableBrushCreate ? "none" : undefined,
|
|
499
|
+
}, children: [!supportsWebGpu ? (_jsx("p", { children: supportsWebGpuMessage })) : null, _jsx("div", { ref: cameraElementRef, style: {
|
|
404
500
|
position: "absolute",
|
|
405
501
|
top: marginTop,
|
|
406
502
|
left: marginLeft,
|
|
@@ -418,5 +514,5 @@ export function Pluot(props) {
|
|
|
418
514
|
}) : {}) })) : (_jsx("canvas", { ref: canvasRef, style: { width, height, border: `${debugMargins ? 1 : 0}px solid black` }, width: width, height: height, ...(bailedEarly ? ({
|
|
419
515
|
['aria-busy']: true,
|
|
420
516
|
['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" })] }));
|
|
517
|
+
}) : {}) })), _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
518
|
}
|
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":"AAAA,OAAO,EAAa,KAAK,eAAe,EAAE,KAAK,wBAAwB,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7H,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAExG,yDAAyD;AACzD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE5D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,eAAe,EAAE,cAAc,CAAC;IAChC,eAAe,EAAE,cAAc,CAAC;IAChC,eAAe,EAAE,eAAe,CAAC;IACjC,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,YAAY,EAAE,YAAY,CAAC;CAC5B,CAAC;AAOF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAsC3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAWnG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,aAAa,EACnB,eAAe,EAAE,cAAc,EAC/B,eAAe,EAAE,cAAc,GAC9B,CAAC,MAAM,EAAE,MAAM,CAAC,CAsBlB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,aAAa,EACnB,eAAe,EAAE,cAAc,EAC/B,eAAe,EAAE,cAAc,GAC9B,WAAW,CAGb;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,aAAa,EACnB,eAAe,EAAE,cAAc,EAC/B,eAAe,EAAE,cAAc,GAC9B,UAAU,CAkBZ;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAK1G;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EACtB,IAAI,EAAE,aAAa,EACnB,KAAK,GAAE,iBAA0B,GAChC,WAAW,EAAE,CAWf;AAED,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,gBAAgB,GAAG,IAAI,CAYvF;AAKD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAmB5D;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,GAClB,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAyBzB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CAW9D;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAY5G;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,GAAG;IAClF,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAYA;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAiBjG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWlG"}
|