@yggy-tech/graphraum 0.36.0 → 0.37.0
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.d.ts +23 -1
- package/dist/index.js +278 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,16 @@ interface GraphraumSetDataOptions {
|
|
|
80
80
|
*/
|
|
81
81
|
fit?: boolean;
|
|
82
82
|
}
|
|
83
|
+
type GraphraumNodeDragPhase = "start" | "move" | "end" | "cancel";
|
|
84
|
+
/** One step of a node drag. `cancel` carries the position the node was restored to (or left at). */
|
|
85
|
+
interface GraphraumNodeDragEvent {
|
|
86
|
+
nodeId: string;
|
|
87
|
+
phase: GraphraumNodeDragPhase;
|
|
88
|
+
position: GraphraumPosition;
|
|
89
|
+
}
|
|
90
|
+
interface GraphraumNodeDragOptions {
|
|
91
|
+
enabled: boolean;
|
|
92
|
+
}
|
|
83
93
|
interface GraphraumCameraPoint3d {
|
|
84
94
|
x: number;
|
|
85
95
|
y: number;
|
|
@@ -618,6 +628,8 @@ declare class Graphraum<NodeAttributes = undefined, EdgeAttributes = undefined>
|
|
|
618
628
|
private gpuTimer;
|
|
619
629
|
private readonly viewListeners;
|
|
620
630
|
private readonly focusListeners;
|
|
631
|
+
private readonly nodeDragListeners;
|
|
632
|
+
private readonly nodeDrag;
|
|
621
633
|
private lastHoveredNodeId;
|
|
622
634
|
private readonly antialias;
|
|
623
635
|
private depthOfField;
|
|
@@ -715,6 +727,16 @@ declare class Graphraum<NodeAttributes = undefined, EdgeAttributes = undefined>
|
|
|
715
727
|
createOverlay(options?: GraphraumOverlayOptions): GraphraumOverlay<NodeAttributes, EdgeAttributes>;
|
|
716
728
|
/** Subscribes to camera and viewport changes. Returns an unsubscribe function. */
|
|
717
729
|
onViewChange(listener: () => void): () => boolean;
|
|
730
|
+
/**
|
|
731
|
+
* Turns node dragging on or off (default off). A drag starts when the pointer moves past a small
|
|
732
|
+
* tolerance after a press on a node (a finger must rest on the node first, so a swipe still pans
|
|
733
|
+
* the camera), moves the node on a plane facing the camera, and ends on release. Escape or a
|
|
734
|
+
* cancelled pointer put the node back. Not available while the density LOD shows clusters.
|
|
735
|
+
*/
|
|
736
|
+
setNodeDrag(options: GraphraumNodeDragOptions): void;
|
|
737
|
+
/** Subscribes to node drag steps (`start`, `move`, `end`, `cancel`). Returns an unsubscribe function. */
|
|
738
|
+
onNodeDrag(listener: (event: GraphraumNodeDragEvent) => void): () => boolean;
|
|
739
|
+
private createNodeDragHost;
|
|
718
740
|
/** Subscribes to selection and hover changes that affect overlay focus chrome. */
|
|
719
741
|
onFocusChange(listener: () => void): () => boolean;
|
|
720
742
|
private notifyFocusChange;
|
|
@@ -983,4 +1005,4 @@ declare function isTransparentGraphraumBackground(background: GraphraumBackgroun
|
|
|
983
1005
|
/** Defines typed, serializable graph visuals without coupling application data to the render loop. */
|
|
984
1006
|
declare function defineVisuals<NodeAttributes = undefined, EdgeAttributes = undefined>(visuals: GraphraumVisualMapper<NodeAttributes, EdgeAttributes>): GraphraumVisualMapper<NodeAttributes, EdgeAttributes>;
|
|
985
1007
|
//#endregion
|
|
986
|
-
export { type CompiledGraphraumPresentation, DEFAULT_ENDPOINT_ATTACH, DEFAULT_ENDPOINT_CLEARANCE, DEFAULT_FORCE_SETTINGS, type EndpointAttach, type EndpointOutline, type ForceLayoutRequest, type ForceSettings, type GraphologyEdgeAttributes, type GraphologyNodeAttributes, type GraphologyRenderer, Graphraum, type GraphraumAutoOrbitOptions, type GraphraumAutoOrbitStatus, type GraphraumBackground, type GraphraumCameraPoint3d, type GraphraumCameraState, type GraphraumCameraState2d, type GraphraumCameraState3d, type GraphraumColor, type GraphraumData, type GraphraumDataPatch, type GraphraumDepthOfFieldOptions, type GraphraumDepthOfFieldStatus, type GraphraumDepthOfFieldTransition, type GraphraumDepthOfFieldUpdate, type GraphraumDiagnostics, type GraphraumEasing, type GraphraumEdge, type GraphraumEdgeEncoding, type GraphraumEdgeMarker, type GraphraumEdgeMarkerEnd, type GraphraumEdgePath, type GraphraumEdgeState, type GraphraumEdgeStyle, type GraphraumEdgeVisual, type GraphraumFlyToOptions, type GraphraumLabelCandidate, type GraphraumLayoutPositions, type GraphraumMode, type GraphraumNode, type GraphraumNodeEncoding, type GraphraumNodeGeometry, type GraphraumNodeShape, type GraphraumNodeState, type GraphraumNodeUpdate, type GraphraumNodeVisual, type GraphraumOptions, GraphraumOverlay, type GraphraumOverlayNode, type GraphraumOverlayOptions, type GraphraumPatternBackground, type GraphraumPickHit, type GraphraumPosition, type GraphraumPresentation, type GraphraumPresentationAction, type GraphraumPresentationProperty, type GraphraumPropertyValue, type GraphraumScreenPosition, type GraphraumSetDataOptions, type GraphraumTheme, type GraphraumThemeName, type GraphraumVisualMapper, type LabelBudgetCandidate, type RichNodePolicy, type SelectBudgetedLabelIdsInput, type SelectFocusLabelIdsInput, attachPointOnOutline, bindGraphology, boundOverlayIds, computeClusteredForcePositions, computeForcePositions, createForceSimulation, defineVisuals, forceIterationCount, graphraumEdgePaths, graphraumNodeShapes, graphraumTheme, graphraumThemeDark, graphraumThemeLight, graphraumThemes, isTransparentGraphraumBackground, normalizeGraphraumBackground, orderFocusNodeIds, resolveGraphraumTheme, selectBudgetedLabelIds, selectFocusLabelIds, selectRichNodeIds, trimEdgeEndpoints };
|
|
1008
|
+
export { type CompiledGraphraumPresentation, DEFAULT_ENDPOINT_ATTACH, DEFAULT_ENDPOINT_CLEARANCE, DEFAULT_FORCE_SETTINGS, type EndpointAttach, type EndpointOutline, type ForceLayoutRequest, type ForceSettings, type GraphologyEdgeAttributes, type GraphologyNodeAttributes, type GraphologyRenderer, Graphraum, type GraphraumAutoOrbitOptions, type GraphraumAutoOrbitStatus, type GraphraumBackground, type GraphraumCameraPoint3d, type GraphraumCameraState, type GraphraumCameraState2d, type GraphraumCameraState3d, type GraphraumColor, type GraphraumData, type GraphraumDataPatch, type GraphraumDepthOfFieldOptions, type GraphraumDepthOfFieldStatus, type GraphraumDepthOfFieldTransition, type GraphraumDepthOfFieldUpdate, type GraphraumDiagnostics, type GraphraumEasing, type GraphraumEdge, type GraphraumEdgeEncoding, type GraphraumEdgeMarker, type GraphraumEdgeMarkerEnd, type GraphraumEdgePath, type GraphraumEdgeState, type GraphraumEdgeStyle, type GraphraumEdgeVisual, type GraphraumFlyToOptions, type GraphraumLabelCandidate, type GraphraumLayoutPositions, type GraphraumMode, type GraphraumNode, type GraphraumNodeDragEvent, type GraphraumNodeDragOptions, type GraphraumNodeDragPhase, type GraphraumNodeEncoding, type GraphraumNodeGeometry, type GraphraumNodeShape, type GraphraumNodeState, type GraphraumNodeUpdate, type GraphraumNodeVisual, type GraphraumOptions, GraphraumOverlay, type GraphraumOverlayNode, type GraphraumOverlayOptions, type GraphraumPatternBackground, type GraphraumPickHit, type GraphraumPosition, type GraphraumPresentation, type GraphraumPresentationAction, type GraphraumPresentationProperty, type GraphraumPropertyValue, type GraphraumScreenPosition, type GraphraumSetDataOptions, type GraphraumTheme, type GraphraumThemeName, type GraphraumVisualMapper, type LabelBudgetCandidate, type RichNodePolicy, type SelectBudgetedLabelIdsInput, type SelectFocusLabelIdsInput, attachPointOnOutline, bindGraphology, boundOverlayIds, computeClusteredForcePositions, computeForcePositions, createForceSimulation, defineVisuals, forceIterationCount, graphraumEdgePaths, graphraumNodeShapes, graphraumTheme, graphraumThemeDark, graphraumThemeLight, graphraumThemes, isTransparentGraphraumBackground, normalizeGraphraumBackground, orderFocusNodeIds, resolveGraphraumTheme, selectBudgetedLabelIds, selectFocusLabelIds, selectRichNodeIds, trimEdgeEndpoints };
|
package/dist/index.js
CHANGED
|
@@ -2493,6 +2493,231 @@ function prepareLayoutPositions(nodes, nodeIndices, layout) {
|
|
|
2493
2493
|
};
|
|
2494
2494
|
});
|
|
2495
2495
|
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Drags one node with the pointer. A press on a node arms a gesture; once the pointer leaves the
|
|
2498
|
+
* click tolerance (a finger must rest on the node first) the node follows it until release.
|
|
2499
|
+
* Camera controls are switched off for the gesture and restored when it ends, so a press that stays
|
|
2500
|
+
* a click behaves as before. Escape, a cancelled pointer or lost pointer capture put the node back.
|
|
2501
|
+
*/
|
|
2502
|
+
var NodeDragController = class {
|
|
2503
|
+
host;
|
|
2504
|
+
holdMs;
|
|
2505
|
+
enabled = false;
|
|
2506
|
+
gesture = null;
|
|
2507
|
+
unlisten;
|
|
2508
|
+
unlistenKeys = null;
|
|
2509
|
+
constructor(host, holdMs = 350) {
|
|
2510
|
+
this.host = host;
|
|
2511
|
+
this.holdMs = holdMs;
|
|
2512
|
+
this.unlisten = [
|
|
2513
|
+
listen(host.element, "pointerdown", this.handleDown, { capture: true }),
|
|
2514
|
+
listen(host.element, "pointermove", this.handleMove),
|
|
2515
|
+
listen(host.element, "pointerup", this.handleUp),
|
|
2516
|
+
listen(host.element, "pointercancel", this.handleCancel),
|
|
2517
|
+
listen(host.element, "lostpointercapture", this.handleCancel)
|
|
2518
|
+
];
|
|
2519
|
+
}
|
|
2520
|
+
/** True from the first drag step until the node is dropped or the drag is cancelled. */
|
|
2521
|
+
get active() {
|
|
2522
|
+
return this.gesture?.phase === "dragging";
|
|
2523
|
+
}
|
|
2524
|
+
setEnabled(enabled) {
|
|
2525
|
+
if (!enabled) this.cancel({ restore: true });
|
|
2526
|
+
this.enabled = enabled;
|
|
2527
|
+
}
|
|
2528
|
+
/** Ends a running gesture as cancelled. `restore` puts the node back where it was grabbed. */
|
|
2529
|
+
cancel({ restore }) {
|
|
2530
|
+
const gesture = this.gesture;
|
|
2531
|
+
if (!gesture) return;
|
|
2532
|
+
if (gesture.phase === "dragging") {
|
|
2533
|
+
const restored = restore && this.host.nodePosition(gesture.nodeId) !== null;
|
|
2534
|
+
if (restored) this.host.moveNode(gesture.nodeId, gesture.origin);
|
|
2535
|
+
this.finish(gesture);
|
|
2536
|
+
this.host.emit({
|
|
2537
|
+
nodeId: gesture.nodeId,
|
|
2538
|
+
phase: "cancel",
|
|
2539
|
+
position: restored ? gesture.origin : gesture.last
|
|
2540
|
+
});
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
this.finish(gesture);
|
|
2544
|
+
}
|
|
2545
|
+
dispose() {
|
|
2546
|
+
this.cancel({ restore: false });
|
|
2547
|
+
this.enabled = false;
|
|
2548
|
+
for (const stop of this.unlisten) stop();
|
|
2549
|
+
}
|
|
2550
|
+
handleDown = (event) => {
|
|
2551
|
+
if (!this.enabled || this.gesture || event.button !== 0 || !this.host.canStart()) return;
|
|
2552
|
+
const nodeId = this.host.pickNode(event.clientX, event.clientY);
|
|
2553
|
+
if (nodeId === null) return;
|
|
2554
|
+
const origin = this.host.nodePosition(nodeId);
|
|
2555
|
+
if (!origin) return;
|
|
2556
|
+
const surface = this.host.createSurface(origin);
|
|
2557
|
+
const grabbed = surface?.(event.clientX, event.clientY);
|
|
2558
|
+
if (!surface || !grabbed) return;
|
|
2559
|
+
const touch = event.pointerType === "touch";
|
|
2560
|
+
const gesture = {
|
|
2561
|
+
controlsHeld: false,
|
|
2562
|
+
holdTimer: null,
|
|
2563
|
+
last: origin,
|
|
2564
|
+
nodeId,
|
|
2565
|
+
offset: {
|
|
2566
|
+
x: origin.x - grabbed.x,
|
|
2567
|
+
y: origin.y - grabbed.y,
|
|
2568
|
+
z: (origin.z ?? 0) - (grabbed.z ?? 0)
|
|
2569
|
+
},
|
|
2570
|
+
origin,
|
|
2571
|
+
phase: "pressed",
|
|
2572
|
+
pointerId: event.pointerId,
|
|
2573
|
+
startX: event.clientX,
|
|
2574
|
+
startY: event.clientY,
|
|
2575
|
+
surface,
|
|
2576
|
+
touch
|
|
2577
|
+
};
|
|
2578
|
+
this.gesture = gesture;
|
|
2579
|
+
this.unlistenKeys = listen(this.host.document, "keydown", this.handleKey);
|
|
2580
|
+
if (touch) {
|
|
2581
|
+
gesture.holdTimer = setTimeout(() => this.beginDrag(gesture), this.holdMs);
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
this.holdControls(gesture);
|
|
2585
|
+
capture(this.host.element, event.pointerId);
|
|
2586
|
+
};
|
|
2587
|
+
handleMove = (event) => {
|
|
2588
|
+
const gesture = this.gesture;
|
|
2589
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
2590
|
+
if (gesture.phase === "pressed") {
|
|
2591
|
+
if (Math.hypot(event.clientX - gesture.startX, event.clientY - gesture.startY) <= 5) return;
|
|
2592
|
+
if (gesture.touch) {
|
|
2593
|
+
this.finish(gesture);
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
this.beginDrag(gesture);
|
|
2597
|
+
}
|
|
2598
|
+
this.follow(gesture, event.clientX, event.clientY);
|
|
2599
|
+
};
|
|
2600
|
+
handleUp = (event) => {
|
|
2601
|
+
const gesture = this.gesture;
|
|
2602
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
2603
|
+
this.finish(gesture);
|
|
2604
|
+
if (gesture.phase === "dragging") this.host.emit({
|
|
2605
|
+
nodeId: gesture.nodeId,
|
|
2606
|
+
phase: "end",
|
|
2607
|
+
position: gesture.last
|
|
2608
|
+
});
|
|
2609
|
+
};
|
|
2610
|
+
handleCancel = (event) => {
|
|
2611
|
+
if (this.gesture && event.pointerId === this.gesture.pointerId) this.cancel({ restore: true });
|
|
2612
|
+
};
|
|
2613
|
+
handleKey = (event) => {
|
|
2614
|
+
if (event.key === "Escape") this.cancel({ restore: true });
|
|
2615
|
+
};
|
|
2616
|
+
beginDrag(gesture) {
|
|
2617
|
+
if (this.gesture !== gesture || gesture.phase === "dragging") return;
|
|
2618
|
+
gesture.phase = "dragging";
|
|
2619
|
+
clearHold(gesture);
|
|
2620
|
+
this.holdControls(gesture);
|
|
2621
|
+
if (gesture.touch) capture(this.host.element, gesture.pointerId);
|
|
2622
|
+
this.host.emit({
|
|
2623
|
+
nodeId: gesture.nodeId,
|
|
2624
|
+
phase: "start",
|
|
2625
|
+
position: gesture.origin
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
follow(gesture, clientX, clientY) {
|
|
2629
|
+
if (!this.host.nodePosition(gesture.nodeId)) {
|
|
2630
|
+
this.cancel({ restore: false });
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
const point = gesture.surface(clientX, clientY);
|
|
2634
|
+
if (!point) return;
|
|
2635
|
+
const z = point.z === void 0 ? gesture.origin.z : point.z + gesture.offset.z;
|
|
2636
|
+
const position = {
|
|
2637
|
+
x: point.x + gesture.offset.x,
|
|
2638
|
+
y: point.y + gesture.offset.y,
|
|
2639
|
+
...z === void 0 ? {} : { z }
|
|
2640
|
+
};
|
|
2641
|
+
gesture.last = position;
|
|
2642
|
+
this.host.moveNode(gesture.nodeId, position);
|
|
2643
|
+
this.host.emit({
|
|
2644
|
+
nodeId: gesture.nodeId,
|
|
2645
|
+
phase: "move",
|
|
2646
|
+
position
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
holdControls(gesture) {
|
|
2650
|
+
if (gesture.controlsHeld) return;
|
|
2651
|
+
gesture.controlsHeld = true;
|
|
2652
|
+
this.host.setControlsEnabled(false);
|
|
2653
|
+
}
|
|
2654
|
+
/** Drops the gesture and everything it held: timer, pointer capture, key listener, camera controls. */
|
|
2655
|
+
finish(gesture) {
|
|
2656
|
+
if (this.gesture !== gesture) return;
|
|
2657
|
+
this.gesture = null;
|
|
2658
|
+
clearHold(gesture);
|
|
2659
|
+
this.unlistenKeys?.();
|
|
2660
|
+
this.unlistenKeys = null;
|
|
2661
|
+
release(this.host.element, gesture.pointerId);
|
|
2662
|
+
if (gesture.controlsHeld) this.host.setControlsEnabled(true);
|
|
2663
|
+
}
|
|
2664
|
+
};
|
|
2665
|
+
/** Typed `addEventListener` for the pointer and key events this module handles; returns the unsubscribe. */
|
|
2666
|
+
function listen(target, type, handler, options) {
|
|
2667
|
+
const listener = handler;
|
|
2668
|
+
target.addEventListener(type, listener, options);
|
|
2669
|
+
return () => target.removeEventListener(type, listener, options);
|
|
2670
|
+
}
|
|
2671
|
+
function clearHold(gesture) {
|
|
2672
|
+
if (gesture.holdTimer === null) return;
|
|
2673
|
+
clearTimeout(gesture.holdTimer);
|
|
2674
|
+
gesture.holdTimer = null;
|
|
2675
|
+
}
|
|
2676
|
+
function capture(element, pointerId) {
|
|
2677
|
+
try {
|
|
2678
|
+
element.setPointerCapture(pointerId);
|
|
2679
|
+
} catch {}
|
|
2680
|
+
}
|
|
2681
|
+
function release(element, pointerId) {
|
|
2682
|
+
try {
|
|
2683
|
+
element.releasePointerCapture(pointerId);
|
|
2684
|
+
} catch {}
|
|
2685
|
+
}
|
|
2686
|
+
//#endregion
|
|
2687
|
+
//#region src/node-drag-surface.ts
|
|
2688
|
+
/**
|
|
2689
|
+
* Where the pointer is in world space on the plane a dragged node moves in. 2D unprojects onto the
|
|
2690
|
+
* view plane and reports no depth. 3D intersects the pointer ray with a plane through the anchor
|
|
2691
|
+
* that faces the camera as it was at the press, so the node keeps its depth and follows the pointer
|
|
2692
|
+
* across the screen. Returns `null` for an empty canvas, and from the surface when the ray misses.
|
|
2693
|
+
*/
|
|
2694
|
+
function createNodeDragSurface({ anchor, bounds, camera, raycaster }) {
|
|
2695
|
+
const initial = bounds();
|
|
2696
|
+
if (initial.width === 0 || initial.height === 0) return null;
|
|
2697
|
+
const ndc = new Vector2();
|
|
2698
|
+
const toNdc = (clientX, clientY) => {
|
|
2699
|
+
const { height, left, top, width } = bounds();
|
|
2700
|
+
return ndc.set((clientX - left) / width * 2 - 1, -((clientY - top) / height) * 2 + 1);
|
|
2701
|
+
};
|
|
2702
|
+
if (camera.type === "OrthographicCamera") return (clientX, clientY) => {
|
|
2703
|
+
const point = toNdc(clientX, clientY);
|
|
2704
|
+
const world = new Vector3(point.x, point.y, 0).unproject(camera);
|
|
2705
|
+
return {
|
|
2706
|
+
x: world.x,
|
|
2707
|
+
y: world.y
|
|
2708
|
+
};
|
|
2709
|
+
};
|
|
2710
|
+
const plane = new Plane().setFromNormalAndCoplanarPoint(camera.getWorldDirection(new Vector3()), new Vector3(anchor.x, anchor.y, anchor.z ?? 0));
|
|
2711
|
+
const hit = new Vector3();
|
|
2712
|
+
return (clientX, clientY) => {
|
|
2713
|
+
raycaster.setFromCamera(toNdc(clientX, clientY), camera);
|
|
2714
|
+
return raycaster.ray.intersectPlane(plane, hit) ? {
|
|
2715
|
+
x: hit.x,
|
|
2716
|
+
y: hit.y,
|
|
2717
|
+
z: hit.z
|
|
2718
|
+
} : null;
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2496
2721
|
const vertexShader$1 = `
|
|
2497
2722
|
attribute float instanceGlowOpacity;
|
|
2498
2723
|
varying vec3 glowColor;
|
|
@@ -3386,6 +3611,8 @@ var Graphraum = class {
|
|
|
3386
3611
|
gpuTimer = null;
|
|
3387
3612
|
viewListeners = /* @__PURE__ */ new Set();
|
|
3388
3613
|
focusListeners = /* @__PURE__ */ new Set();
|
|
3614
|
+
nodeDragListeners = /* @__PURE__ */ new Set();
|
|
3615
|
+
nodeDrag;
|
|
3389
3616
|
lastHoveredNodeId = null;
|
|
3390
3617
|
antialias;
|
|
3391
3618
|
depthOfField = DEPTH_OF_FIELD_DEFAULTS;
|
|
@@ -3433,6 +3660,7 @@ var Graphraum = class {
|
|
|
3433
3660
|
this.renderer.domElement.addEventListener("pointermove", this.handlePointerMove);
|
|
3434
3661
|
this.renderer.domElement.addEventListener("pointerleave", this.handlePointerLeave);
|
|
3435
3662
|
for (const type of FLIGHT_CANCEL_EVENTS) this.renderer.domElement.addEventListener(type, this.cancelFlight);
|
|
3663
|
+
this.nodeDrag = new NodeDragController(this.createNodeDragHost());
|
|
3436
3664
|
this.camera = this.createCamera();
|
|
3437
3665
|
this.controls = this.createControls();
|
|
3438
3666
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
@@ -3586,6 +3814,7 @@ var Graphraum = class {
|
|
|
3586
3814
|
this.requestRender();
|
|
3587
3815
|
}
|
|
3588
3816
|
replaceData(data, fitView) {
|
|
3817
|
+
this.nodeDrag.cancel({ restore: false });
|
|
3589
3818
|
const compiled = compileGraph(data, this.visuals);
|
|
3590
3819
|
this.disposeGraphObjects();
|
|
3591
3820
|
this.data = {
|
|
@@ -3903,6 +4132,7 @@ var Graphraum = class {
|
|
|
3903
4132
|
setMode(mode) {
|
|
3904
4133
|
if (mode === this.mode) return;
|
|
3905
4134
|
this.cancelFlight();
|
|
4135
|
+
this.nodeDrag.cancel({ restore: true });
|
|
3906
4136
|
this.stopAutoOrbit();
|
|
3907
4137
|
this.mode = mode;
|
|
3908
4138
|
this.controls.removeEventListener("change", this.handleViewChange);
|
|
@@ -4030,6 +4260,51 @@ var Graphraum = class {
|
|
|
4030
4260
|
listener();
|
|
4031
4261
|
return () => this.viewListeners.delete(listener);
|
|
4032
4262
|
}
|
|
4263
|
+
/**
|
|
4264
|
+
* Turns node dragging on or off (default off). A drag starts when the pointer moves past a small
|
|
4265
|
+
* tolerance after a press on a node (a finger must rest on the node first, so a swipe still pans
|
|
4266
|
+
* the camera), moves the node on a plane facing the camera, and ends on release. Escape or a
|
|
4267
|
+
* cancelled pointer put the node back. Not available while the density LOD shows clusters.
|
|
4268
|
+
*/
|
|
4269
|
+
setNodeDrag(options) {
|
|
4270
|
+
if (typeof options?.enabled !== "boolean") throw new Error("setNodeDrag option enabled must be a boolean.");
|
|
4271
|
+
this.nodeDrag.setEnabled(options.enabled);
|
|
4272
|
+
}
|
|
4273
|
+
/** Subscribes to node drag steps (`start`, `move`, `end`, `cancel`). Returns an unsubscribe function. */
|
|
4274
|
+
onNodeDrag(listener) {
|
|
4275
|
+
this.nodeDragListeners.add(listener);
|
|
4276
|
+
return () => this.nodeDragListeners.delete(listener);
|
|
4277
|
+
}
|
|
4278
|
+
createNodeDragHost() {
|
|
4279
|
+
const element = this.renderer.domElement;
|
|
4280
|
+
return {
|
|
4281
|
+
canStart: () => !this.destroyed && this.nodeMesh !== null && !this.densityLodActive,
|
|
4282
|
+
createSurface: (anchor) => createNodeDragSurface({
|
|
4283
|
+
anchor,
|
|
4284
|
+
bounds: () => element.getBoundingClientRect(),
|
|
4285
|
+
camera: this.camera,
|
|
4286
|
+
raycaster: this.raycaster
|
|
4287
|
+
}),
|
|
4288
|
+
document: element.ownerDocument,
|
|
4289
|
+
element,
|
|
4290
|
+
emit: (event) => {
|
|
4291
|
+
for (const listener of this.nodeDragListeners) listener(event);
|
|
4292
|
+
},
|
|
4293
|
+
moveNode: (nodeId, position) => this.updateNodes([{
|
|
4294
|
+
id: nodeId,
|
|
4295
|
+
position
|
|
4296
|
+
}]),
|
|
4297
|
+
nodePosition: (nodeId) => {
|
|
4298
|
+
const index = this.nodeIndices.get(nodeId);
|
|
4299
|
+
const node = index === void 0 ? void 0 : this.data.nodes[index];
|
|
4300
|
+
return node ? { ...node.position } : null;
|
|
4301
|
+
},
|
|
4302
|
+
pickNode: (clientX, clientY) => this.pickNode(clientX, clientY),
|
|
4303
|
+
setControlsEnabled: (enabled) => {
|
|
4304
|
+
this.controls.enabled = enabled;
|
|
4305
|
+
}
|
|
4306
|
+
};
|
|
4307
|
+
}
|
|
4033
4308
|
/** Subscribes to selection and hover changes that affect overlay focus chrome. */
|
|
4034
4309
|
onFocusChange(listener) {
|
|
4035
4310
|
this.focusListeners.add(listener);
|
|
@@ -4040,6 +4315,7 @@ var Graphraum = class {
|
|
|
4040
4315
|
for (const listener of this.focusListeners) listener();
|
|
4041
4316
|
}
|
|
4042
4317
|
handlePointerMove = (event) => {
|
|
4318
|
+
if (this.nodeDrag.active) return;
|
|
4043
4319
|
const nodeId = this.pickNode(event.clientX, event.clientY);
|
|
4044
4320
|
if (nodeId === this.lastHoveredNodeId) return;
|
|
4045
4321
|
this.lastHoveredNodeId = nodeId;
|
|
@@ -4396,6 +4672,7 @@ var Graphraum = class {
|
|
|
4396
4672
|
destroy() {
|
|
4397
4673
|
this.destroyed = true;
|
|
4398
4674
|
this.cancelFlight();
|
|
4675
|
+
this.nodeDrag.dispose();
|
|
4399
4676
|
this.stopAutoOrbit();
|
|
4400
4677
|
this.resizeObserver.disconnect();
|
|
4401
4678
|
this.renderer.domElement.removeEventListener("pointermove", this.handlePointerMove);
|
|
@@ -4540,6 +4817,7 @@ var Graphraum = class {
|
|
|
4540
4817
|
* without a loop.
|
|
4541
4818
|
*/
|
|
4542
4819
|
stepAutoOrbit = (deltaSeconds) => {
|
|
4820
|
+
if (this.nodeDrag.active) return;
|
|
4543
4821
|
try {
|
|
4544
4822
|
this.controls.autoRotate = true;
|
|
4545
4823
|
this.orbitUpdating = true;
|