@cocorof/graphier 0.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/index.cjs +378 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +43 -2
- package/dist/index.js +378 -77
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1721,27 +1721,31 @@ function applyNodeHighlight(nodesMesh, nodes, selectedNodeId, highlightSet, them
|
|
|
1721
1721
|
} else if (node.id === selectedNodeId) {
|
|
1722
1722
|
tmpColor.set(brightColor);
|
|
1723
1723
|
brightTmp.setRGB(1, 1, 1);
|
|
1724
|
-
tmpColor.lerp(brightTmp, 0.
|
|
1724
|
+
tmpColor.lerp(brightTmp, 0.65);
|
|
1725
|
+
tmpColor.multiplyScalar(1.4);
|
|
1726
|
+
tmpColor.r = Math.min(tmpColor.r, 2);
|
|
1727
|
+
tmpColor.g = Math.min(tmpColor.g, 2);
|
|
1728
|
+
tmpColor.b = Math.min(tmpColor.b, 2);
|
|
1725
1729
|
} else if (highlightSet.has(node.id)) {
|
|
1726
1730
|
const hop = highlightSet.get(node.id);
|
|
1727
1731
|
if (hop === 1) {
|
|
1728
|
-
tmpColor.set(brightColor);
|
|
1732
|
+
tmpColor.set(brightColor).multiplyScalar(1.2);
|
|
1729
1733
|
} else if (hop === 2) {
|
|
1730
1734
|
tmpColor.set(baseColor);
|
|
1731
1735
|
brightTmp.set(brightColor);
|
|
1732
|
-
tmpColor.lerp(brightTmp, 0.
|
|
1736
|
+
tmpColor.lerp(brightTmp, 0.5);
|
|
1733
1737
|
} else {
|
|
1734
|
-
tmpColor.set(baseColor).multiplyScalar(0.
|
|
1738
|
+
tmpColor.set(baseColor).multiplyScalar(0.6);
|
|
1735
1739
|
}
|
|
1736
1740
|
} else {
|
|
1737
|
-
tmpColor.set(baseColor).multiplyScalar(0.
|
|
1741
|
+
tmpColor.set(baseColor).multiplyScalar(0.04);
|
|
1738
1742
|
}
|
|
1739
1743
|
nodesMesh.setColorAt(i, tmpColor);
|
|
1740
1744
|
}
|
|
1741
1745
|
if (nodesMesh.instanceColor) nodesMesh.instanceColor.needsUpdate = true;
|
|
1742
1746
|
const mat = nodesMesh.material;
|
|
1743
1747
|
if ((_a = mat.uniforms) == null ? void 0 : _a.uGlowIntensity) {
|
|
1744
|
-
mat.uniforms.uGlowIntensity.value = highlightSet ? 1.
|
|
1748
|
+
mat.uniforms.uGlowIntensity.value = highlightSet ? 1.5 : 1;
|
|
1745
1749
|
}
|
|
1746
1750
|
}
|
|
1747
1751
|
function applyEdgeHighlight(edgesMesh, links, edgeNodeIndices, edgeLinkIndices, highlightSet, edgeOpacity, nodeCount, theme) {
|
|
@@ -1769,7 +1773,7 @@ function applyEdgeHighlight(edgesMesh, links, edgeNodeIndices, edgeLinkIndices,
|
|
|
1769
1773
|
tmpColor.multiplyScalar(1.2);
|
|
1770
1774
|
}
|
|
1771
1775
|
} else {
|
|
1772
|
-
tmpColor.setRGB(0.
|
|
1776
|
+
tmpColor.setRGB(0.01, 0.01, 0.02);
|
|
1773
1777
|
}
|
|
1774
1778
|
colorArr[i * 6 + 0] = tmpColor.r;
|
|
1775
1779
|
colorArr[i * 6 + 1] = tmpColor.g;
|
|
@@ -1856,6 +1860,9 @@ function createLabelSystem(maxLabels) {
|
|
|
1856
1860
|
}
|
|
1857
1861
|
function defaultLabelText$1(node) {
|
|
1858
1862
|
const label = node.label ?? node.id ?? "";
|
|
1863
|
+
if (node.type === "repo" && label.includes("/")) {
|
|
1864
|
+
return label.split("/").pop();
|
|
1865
|
+
}
|
|
1859
1866
|
return label.length > 30 ? label.substring(0, 27) + "…" : label;
|
|
1860
1867
|
}
|
|
1861
1868
|
function getOrCreateTexture(cache, nodeId, text, color) {
|
|
@@ -1894,7 +1901,7 @@ function getOrCreateTexture(cache, nodeId, text, color) {
|
|
|
1894
1901
|
}
|
|
1895
1902
|
return entry;
|
|
1896
1903
|
}
|
|
1897
|
-
function updateLabels(state, nodes, positions, scales, camera, theme, showLabels, labelScale, labelThreshold, maxLabels, labelFormatter) {
|
|
1904
|
+
function updateLabels(state, nodes, positions, scales, camera, theme, showLabels, labelScale, labelThreshold, maxLabels, labelFormatter, highlightSet) {
|
|
1898
1905
|
if (!positions || nodes.length === 0 || !showLabels) {
|
|
1899
1906
|
for (const sp of state.sprites) sp.visible = false;
|
|
1900
1907
|
return;
|
|
@@ -1908,6 +1915,7 @@ function updateLabels(state, nodes, positions, scales, camera, theme, showLabels
|
|
|
1908
1915
|
const maxDistSq = maxDist * maxDist;
|
|
1909
1916
|
const candidates = [];
|
|
1910
1917
|
for (let i = 0; i < nc; i++) {
|
|
1918
|
+
if (highlightSet && !highlightSet.has(nodes[i].id)) continue;
|
|
1911
1919
|
const dx = positions[i * 3] - camPos.x;
|
|
1912
1920
|
const dy = positions[i * 3 + 1] - camPos.y;
|
|
1913
1921
|
const dz = positions[i * 3 + 2] - camPos.z;
|
|
@@ -1953,58 +1961,48 @@ function disposeLabelSystem(state) {
|
|
|
1953
1961
|
}
|
|
1954
1962
|
state.textureCache.clear();
|
|
1955
1963
|
}
|
|
1956
|
-
const
|
|
1957
|
-
const
|
|
1958
|
-
const
|
|
1959
|
-
const
|
|
1960
|
-
const
|
|
1964
|
+
const ORBIT_ACCEL = 12e-4;
|
|
1965
|
+
const ORBIT_MAX_SPEED = 0.06;
|
|
1966
|
+
const ORBIT_DAMPING = 0.88;
|
|
1967
|
+
const ORBIT_ZOOM_FACTOR = 0.015;
|
|
1968
|
+
const ORBIT_DEAD_ZONE = 1e-4;
|
|
1969
|
+
const FLY_ACCEL = 6e-3;
|
|
1970
|
+
const FLY_FRICTION = 0.92;
|
|
1971
|
+
const FLY_MAX_THRUST = 0.18;
|
|
1972
|
+
const FLY_TURN_RATE = 8e-3;
|
|
1973
|
+
const FLY_DEAD_ZONE = 2e-4;
|
|
1961
1974
|
const _offset = new THREE__namespace.Vector3();
|
|
1962
1975
|
const _right = new THREE__namespace.Vector3();
|
|
1963
1976
|
const _q = new THREE__namespace.Quaternion();
|
|
1964
|
-
|
|
1965
|
-
|
|
1977
|
+
const _dir = new THREE__namespace.Vector3();
|
|
1978
|
+
function clamp(v, min, max) {
|
|
1979
|
+
return v < min ? min : v > max ? max : v;
|
|
1980
|
+
}
|
|
1981
|
+
function createOrbitUpdate(camera, controls, keys) {
|
|
1966
1982
|
const vel = { zoom: 0, azimuth: 0, polar: 0 };
|
|
1967
|
-
function
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
if (
|
|
1971
|
-
keys[
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
vel.
|
|
1979
|
-
vel.
|
|
1980
|
-
vel.
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
container.addEventListener("keyup", onKeyUp);
|
|
1984
|
-
container.addEventListener("blur", onBlur);
|
|
1985
|
-
function update() {
|
|
1986
|
-
if (keys["z"] || keys["Z"]) vel.zoom -= ACCEL;
|
|
1987
|
-
if (keys["x"] || keys["X"]) vel.zoom += ACCEL;
|
|
1988
|
-
if (keys["ArrowLeft"]) vel.azimuth -= ACCEL;
|
|
1989
|
-
if (keys["ArrowRight"]) vel.azimuth += ACCEL;
|
|
1990
|
-
if (keys["ArrowUp"]) vel.polar -= ACCEL;
|
|
1991
|
-
if (keys["ArrowDown"]) vel.polar += ACCEL;
|
|
1992
|
-
vel.zoom = clamp(vel.zoom, -MAX_SPEED, MAX_SPEED);
|
|
1993
|
-
vel.azimuth = clamp(vel.azimuth, -MAX_SPEED, MAX_SPEED);
|
|
1994
|
-
vel.polar = clamp(vel.polar, -MAX_SPEED, MAX_SPEED);
|
|
1995
|
-
vel.zoom *= DAMPING;
|
|
1996
|
-
vel.azimuth *= DAMPING;
|
|
1997
|
-
vel.polar *= DAMPING;
|
|
1998
|
-
if (Math.abs(vel.zoom) < DEAD_ZONE) vel.zoom = 0;
|
|
1999
|
-
if (Math.abs(vel.azimuth) < DEAD_ZONE) vel.azimuth = 0;
|
|
2000
|
-
if (Math.abs(vel.polar) < DEAD_ZONE) vel.polar = 0;
|
|
1983
|
+
return function update() {
|
|
1984
|
+
if (keys["z"] || keys["Z"]) vel.zoom -= ORBIT_ACCEL;
|
|
1985
|
+
if (keys["x"] || keys["X"]) vel.zoom += ORBIT_ACCEL;
|
|
1986
|
+
if (keys["ArrowLeft"]) vel.azimuth -= ORBIT_ACCEL;
|
|
1987
|
+
if (keys["ArrowRight"]) vel.azimuth += ORBIT_ACCEL;
|
|
1988
|
+
if (keys["ArrowUp"]) vel.polar -= ORBIT_ACCEL;
|
|
1989
|
+
if (keys["ArrowDown"]) vel.polar += ORBIT_ACCEL;
|
|
1990
|
+
vel.zoom = clamp(vel.zoom, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
|
|
1991
|
+
vel.azimuth = clamp(vel.azimuth, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
|
|
1992
|
+
vel.polar = clamp(vel.polar, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
|
|
1993
|
+
vel.zoom *= ORBIT_DAMPING;
|
|
1994
|
+
vel.azimuth *= ORBIT_DAMPING;
|
|
1995
|
+
vel.polar *= ORBIT_DAMPING;
|
|
1996
|
+
if (Math.abs(vel.zoom) < ORBIT_DEAD_ZONE) vel.zoom = 0;
|
|
1997
|
+
if (Math.abs(vel.azimuth) < ORBIT_DEAD_ZONE) vel.azimuth = 0;
|
|
1998
|
+
if (Math.abs(vel.polar) < ORBIT_DEAD_ZONE) vel.polar = 0;
|
|
2001
1999
|
const hasMotion = vel.zoom !== 0 || vel.azimuth !== 0 || vel.polar !== 0;
|
|
2002
2000
|
if (!hasMotion) return false;
|
|
2003
2001
|
const target = controls.target;
|
|
2004
2002
|
_offset.copy(camera.position).sub(target);
|
|
2005
2003
|
let radius = _offset.length();
|
|
2006
2004
|
if (vel.zoom !== 0) {
|
|
2007
|
-
radius *= 1 + vel.zoom *
|
|
2005
|
+
radius *= 1 + vel.zoom * ORBIT_ZOOM_FACTOR * radius * 0.01;
|
|
2008
2006
|
radius = Math.max(controls.minDistance, Math.min(controls.maxDistance, radius));
|
|
2009
2007
|
}
|
|
2010
2008
|
if (vel.azimuth !== 0) {
|
|
@@ -2025,16 +2023,72 @@ function setupKeyboardControls(camera, controls, container) {
|
|
|
2025
2023
|
camera.lookAt(target);
|
|
2026
2024
|
controls.target.copy(target);
|
|
2027
2025
|
return true;
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
function createFlyUpdate(camera, controls, keys) {
|
|
2029
|
+
const vel = { thrust: 0, yaw: 0, pitch: 0 };
|
|
2030
|
+
return function update() {
|
|
2031
|
+
if (keys["z"] || keys["Z"]) vel.thrust += FLY_ACCEL;
|
|
2032
|
+
if (keys["x"] || keys["X"]) vel.thrust -= FLY_ACCEL;
|
|
2033
|
+
if (keys["a"] || keys["A"] || keys["ArrowLeft"]) vel.yaw += FLY_TURN_RATE;
|
|
2034
|
+
if (keys["d"] || keys["D"] || keys["ArrowRight"]) vel.yaw -= FLY_TURN_RATE;
|
|
2035
|
+
if (keys["w"] || keys["W"] || keys["ArrowUp"]) vel.pitch += FLY_TURN_RATE;
|
|
2036
|
+
if (keys["s"] || keys["S"] || keys["ArrowDown"]) vel.pitch -= FLY_TURN_RATE;
|
|
2037
|
+
const lookDist = camera.position.distanceTo(controls.target);
|
|
2038
|
+
const distScale = Math.max(0.3, lookDist * 3e-3);
|
|
2039
|
+
vel.thrust = clamp(vel.thrust, -FLY_MAX_THRUST * distScale, FLY_MAX_THRUST * distScale);
|
|
2040
|
+
vel.yaw = clamp(vel.yaw, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
|
|
2041
|
+
vel.pitch = clamp(vel.pitch, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
|
|
2042
|
+
vel.thrust *= FLY_FRICTION;
|
|
2043
|
+
vel.yaw *= FLY_FRICTION;
|
|
2044
|
+
vel.pitch *= FLY_FRICTION;
|
|
2045
|
+
if (Math.abs(vel.thrust) < FLY_DEAD_ZONE) vel.thrust = 0;
|
|
2046
|
+
if (Math.abs(vel.yaw) < FLY_DEAD_ZONE) vel.yaw = 0;
|
|
2047
|
+
if (Math.abs(vel.pitch) < FLY_DEAD_ZONE) vel.pitch = 0;
|
|
2048
|
+
const hasMotion = vel.thrust !== 0 || vel.yaw !== 0 || vel.pitch !== 0;
|
|
2049
|
+
if (!hasMotion) return false;
|
|
2050
|
+
if (vel.yaw !== 0) {
|
|
2051
|
+
_q.setFromAxisAngle(camera.up, vel.yaw);
|
|
2052
|
+
camera.quaternion.premultiply(_q);
|
|
2053
|
+
}
|
|
2054
|
+
if (vel.pitch !== 0) {
|
|
2055
|
+
_right.setFromMatrixColumn(camera.matrixWorld, 0).normalize();
|
|
2056
|
+
_q.setFromAxisAngle(_right, vel.pitch);
|
|
2057
|
+
camera.quaternion.premultiply(_q);
|
|
2058
|
+
}
|
|
2059
|
+
if (vel.thrust !== 0) {
|
|
2060
|
+
camera.getWorldDirection(_dir);
|
|
2061
|
+
_dir.multiplyScalar(vel.thrust * distScale * 100);
|
|
2062
|
+
camera.position.add(_dir);
|
|
2063
|
+
controls.target.add(_dir);
|
|
2064
|
+
}
|
|
2065
|
+
return true;
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
function setupKeyboardControls(camera, controls, container, mode = "fly") {
|
|
2069
|
+
const keys = {};
|
|
2070
|
+
function onKeyDown2(e) {
|
|
2071
|
+
var _a;
|
|
2072
|
+
const tag = (_a = e.target) == null ? void 0 : _a.tagName;
|
|
2073
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
2074
|
+
keys[e.key] = true;
|
|
2075
|
+
}
|
|
2076
|
+
function onKeyUp(e) {
|
|
2077
|
+
keys[e.key] = false;
|
|
2078
|
+
}
|
|
2079
|
+
function onBlur() {
|
|
2080
|
+
for (const k of Object.keys(keys)) keys[k] = false;
|
|
2028
2081
|
}
|
|
2082
|
+
container.addEventListener("keydown", onKeyDown2);
|
|
2083
|
+
container.addEventListener("keyup", onKeyUp);
|
|
2084
|
+
container.addEventListener("blur", onBlur);
|
|
2085
|
+
const updateFn = mode === "fly" ? createFlyUpdate(camera, controls, keys) : createOrbitUpdate(camera, controls, keys);
|
|
2029
2086
|
function cleanup() {
|
|
2030
2087
|
container.removeEventListener("keydown", onKeyDown2);
|
|
2031
2088
|
container.removeEventListener("keyup", onKeyUp);
|
|
2032
2089
|
container.removeEventListener("blur", onBlur);
|
|
2033
2090
|
}
|
|
2034
|
-
return { update, cleanup };
|
|
2035
|
-
}
|
|
2036
|
-
function clamp(v, min, max) {
|
|
2037
|
-
return v < min ? min : v > max ? max : v;
|
|
2091
|
+
return { update: updateFn, cleanup };
|
|
2038
2092
|
}
|
|
2039
2093
|
function createScene(container, backgroundColor, style, rendererConfig) {
|
|
2040
2094
|
const scene = new THREE__namespace.Scene();
|
|
@@ -2071,7 +2125,8 @@ function createScene(container, backgroundColor, style, rendererConfig) {
|
|
|
2071
2125
|
scene.add(selectionRing);
|
|
2072
2126
|
const labels = createLabelSystem(style.maxLabels);
|
|
2073
2127
|
scene.add(labels.group);
|
|
2074
|
-
const
|
|
2128
|
+
const cameraMode = (rendererConfig == null ? void 0 : rendererConfig.cameraMode) ?? "fly";
|
|
2129
|
+
const keyboard = setupKeyboardControls(camera, controls, container, cameraMode);
|
|
2075
2130
|
return {
|
|
2076
2131
|
scene,
|
|
2077
2132
|
camera,
|
|
@@ -2410,21 +2465,136 @@ function zoomToFitPositions(camera, controls, positions, duration, padding) {
|
|
|
2410
2465
|
duration
|
|
2411
2466
|
);
|
|
2412
2467
|
}
|
|
2413
|
-
|
|
2468
|
+
const EDGE_HIT_THRESHOLD = 5;
|
|
2469
|
+
function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, callbacks, container, getEdgeData) {
|
|
2414
2470
|
const raycaster = new THREE__namespace.Raycaster();
|
|
2415
2471
|
const mouse = new THREE__namespace.Vector2();
|
|
2416
2472
|
let mouseDownPos = null;
|
|
2417
2473
|
let lastClickTime = 0;
|
|
2418
2474
|
let lastClickNodeId = null;
|
|
2419
2475
|
let singleClickTimer = null;
|
|
2476
|
+
let dragNode = null;
|
|
2477
|
+
let isDragging = false;
|
|
2478
|
+
const dragPlane = new THREE__namespace.Plane();
|
|
2479
|
+
const dragIntersect = new THREE__namespace.Vector3();
|
|
2420
2480
|
function onPointerDown2(e) {
|
|
2421
2481
|
mouseDownPos = { x: e.clientX, y: e.clientY };
|
|
2422
2482
|
if (container && document.activeElement !== container) {
|
|
2423
2483
|
container.focus({ preventScroll: true });
|
|
2424
2484
|
}
|
|
2485
|
+
if (callbacks.onNodeDrag) {
|
|
2486
|
+
const nodesMesh = getNodesMesh();
|
|
2487
|
+
if (nodesMesh) {
|
|
2488
|
+
const rect = canvas.getBoundingClientRect();
|
|
2489
|
+
mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
2490
|
+
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
2491
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2492
|
+
const hits = raycaster.intersectObject(nodesMesh);
|
|
2493
|
+
if (hits.length > 0 && hits[0].instanceId != null) {
|
|
2494
|
+
const nodes = getNodes();
|
|
2495
|
+
dragNode = nodes[hits[0].instanceId] ?? null;
|
|
2496
|
+
hits[0].instanceId;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
function onPointerMove2(e) {
|
|
2502
|
+
var _a, _b, _c;
|
|
2503
|
+
if (dragNode && mouseDownPos && callbacks.onNodeDrag) {
|
|
2504
|
+
const dx = Math.abs(e.clientX - mouseDownPos.x);
|
|
2505
|
+
const dy = Math.abs(e.clientY - mouseDownPos.y);
|
|
2506
|
+
if (dx + dy > 5) {
|
|
2507
|
+
isDragging = true;
|
|
2508
|
+
controls.enabled = false;
|
|
2509
|
+
const rect2 = canvas.getBoundingClientRect();
|
|
2510
|
+
mouse.x = (e.clientX - rect2.left) / rect2.width * 2 - 1;
|
|
2511
|
+
mouse.y = -((e.clientY - rect2.top) / rect2.height) * 2 + 1;
|
|
2512
|
+
const nodePos = new THREE__namespace.Vector3(
|
|
2513
|
+
dragNode.x ?? 0,
|
|
2514
|
+
dragNode.y ?? 0,
|
|
2515
|
+
dragNode.z ?? 0
|
|
2516
|
+
);
|
|
2517
|
+
dragPlane.setFromNormalAndCoplanarPoint(
|
|
2518
|
+
camera.getWorldDirection(new THREE__namespace.Vector3()).negate(),
|
|
2519
|
+
nodePos
|
|
2520
|
+
);
|
|
2521
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2522
|
+
if (raycaster.ray.intersectPlane(dragPlane, dragIntersect)) {
|
|
2523
|
+
callbacks.onNodeDrag(dragNode, {
|
|
2524
|
+
x: dragIntersect.x,
|
|
2525
|
+
y: dragIntersect.y,
|
|
2526
|
+
z: dragIntersect.z
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
if (!callbacks.onNodeHover && !callbacks.onLinkHover) return;
|
|
2533
|
+
const nodesMesh = getNodesMesh();
|
|
2534
|
+
if (!nodesMesh) return;
|
|
2535
|
+
const rect = canvas.getBoundingClientRect();
|
|
2536
|
+
mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
2537
|
+
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
2538
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2539
|
+
const hits = raycaster.intersectObject(nodesMesh);
|
|
2540
|
+
if (hits.length > 0 && hits[0].instanceId != null) {
|
|
2541
|
+
const nodes = getNodes();
|
|
2542
|
+
const node = nodes[hits[0].instanceId];
|
|
2543
|
+
if (node && node.id !== lastHoveredId) {
|
|
2544
|
+
lastHoveredId = node.id;
|
|
2545
|
+
lastHoveredLinkIdx = -1;
|
|
2546
|
+
canvas.style.cursor = "pointer";
|
|
2547
|
+
(_a = callbacks.onNodeHover) == null ? void 0 : _a.call(callbacks, node);
|
|
2548
|
+
(_b = callbacks.onLinkHover) == null ? void 0 : _b.call(callbacks, null);
|
|
2549
|
+
}
|
|
2550
|
+
} else {
|
|
2551
|
+
if (lastHoveredId !== null) {
|
|
2552
|
+
lastHoveredId = null;
|
|
2553
|
+
canvas.style.cursor = "default";
|
|
2554
|
+
(_c = callbacks.onNodeHover) == null ? void 0 : _c.call(callbacks, null);
|
|
2555
|
+
}
|
|
2556
|
+
if (callbacks.onLinkHover && getEdgeData) {
|
|
2557
|
+
const edgeData = getEdgeData();
|
|
2558
|
+
if (edgeData == null ? void 0 : edgeData.positions) {
|
|
2559
|
+
const hitLink = findNearestEdge(
|
|
2560
|
+
e.clientX,
|
|
2561
|
+
e.clientY,
|
|
2562
|
+
rect,
|
|
2563
|
+
camera,
|
|
2564
|
+
edgeData
|
|
2565
|
+
);
|
|
2566
|
+
if (hitLink) {
|
|
2567
|
+
if (lastHoveredLinkIdx !== hitLink.index) {
|
|
2568
|
+
lastHoveredLinkIdx = hitLink.index;
|
|
2569
|
+
canvas.style.cursor = "pointer";
|
|
2570
|
+
callbacks.onLinkHover(hitLink.link);
|
|
2571
|
+
}
|
|
2572
|
+
} else if (lastHoveredLinkIdx !== -1) {
|
|
2573
|
+
lastHoveredLinkIdx = -1;
|
|
2574
|
+
canvas.style.cursor = "default";
|
|
2575
|
+
callbacks.onLinkHover(null);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2425
2580
|
}
|
|
2426
2581
|
function onPointerUp2(e) {
|
|
2427
2582
|
var _a;
|
|
2583
|
+
if (isDragging && dragNode && callbacks.onNodeDragEnd) {
|
|
2584
|
+
callbacks.onNodeDragEnd(dragNode, {
|
|
2585
|
+
x: dragNode.x ?? 0,
|
|
2586
|
+
y: dragNode.y ?? 0,
|
|
2587
|
+
z: dragNode.z ?? 0
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
if (isDragging) {
|
|
2591
|
+
isDragging = false;
|
|
2592
|
+
dragNode = null;
|
|
2593
|
+
controls.enabled = true;
|
|
2594
|
+
mouseDownPos = null;
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
dragNode = null;
|
|
2428
2598
|
if (!mouseDownPos) return;
|
|
2429
2599
|
const dx = Math.abs(e.clientX - mouseDownPos.x);
|
|
2430
2600
|
const dy = Math.abs(e.clientY - mouseDownPos.y);
|
|
@@ -2472,6 +2642,22 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2472
2642
|
callbacks.onNodeClick(node);
|
|
2473
2643
|
}, 250);
|
|
2474
2644
|
} else {
|
|
2645
|
+
if (callbacks.onLinkClick && getEdgeData) {
|
|
2646
|
+
const edgeData = getEdgeData();
|
|
2647
|
+
if (edgeData == null ? void 0 : edgeData.positions) {
|
|
2648
|
+
const hitLink = findNearestEdge(
|
|
2649
|
+
e.clientX,
|
|
2650
|
+
e.clientY,
|
|
2651
|
+
rect,
|
|
2652
|
+
camera,
|
|
2653
|
+
edgeData
|
|
2654
|
+
);
|
|
2655
|
+
if (hitLink) {
|
|
2656
|
+
callbacks.onLinkClick(hitLink.link);
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2475
2661
|
if (singleClickTimer) clearTimeout(singleClickTimer);
|
|
2476
2662
|
singleClickTimer = null;
|
|
2477
2663
|
lastClickTime = 0;
|
|
@@ -2479,13 +2665,9 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2479
2665
|
callbacks.onNodeClick(null);
|
|
2480
2666
|
}
|
|
2481
2667
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
}
|
|
2486
|
-
let lastHoveredId = null;
|
|
2487
|
-
function onPointerMove2(e) {
|
|
2488
|
-
if (!callbacks.onNodeHover) return;
|
|
2668
|
+
function onContextMenu2(e) {
|
|
2669
|
+
if (!callbacks.onContextMenu) return;
|
|
2670
|
+
e.preventDefault();
|
|
2489
2671
|
const nodesMesh = getNodesMesh();
|
|
2490
2672
|
if (!nodesMesh) return;
|
|
2491
2673
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -2496,20 +2678,21 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2496
2678
|
if (hits.length > 0 && hits[0].instanceId != null) {
|
|
2497
2679
|
const nodes = getNodes();
|
|
2498
2680
|
const node = nodes[hits[0].instanceId];
|
|
2499
|
-
if (node
|
|
2500
|
-
|
|
2501
|
-
canvas.style.cursor = "pointer";
|
|
2502
|
-
callbacks.onNodeHover(node);
|
|
2681
|
+
if (node) {
|
|
2682
|
+
callbacks.onContextMenu(node, { x: e.clientX, y: e.clientY });
|
|
2503
2683
|
}
|
|
2504
|
-
} else if (lastHoveredId !== null) {
|
|
2505
|
-
lastHoveredId = null;
|
|
2506
|
-
canvas.style.cursor = "default";
|
|
2507
|
-
callbacks.onNodeHover(null);
|
|
2508
2684
|
}
|
|
2509
2685
|
}
|
|
2686
|
+
const keyTarget = container ?? window;
|
|
2687
|
+
function onKeyDown2(e) {
|
|
2688
|
+
if (e.key === "Escape") callbacks.onNodeClick(null);
|
|
2689
|
+
}
|
|
2690
|
+
let lastHoveredId = null;
|
|
2691
|
+
let lastHoveredLinkIdx = -1;
|
|
2510
2692
|
canvas.addEventListener("pointerdown", onPointerDown2);
|
|
2511
2693
|
canvas.addEventListener("pointerup", onPointerUp2);
|
|
2512
2694
|
canvas.addEventListener("pointermove", onPointerMove2);
|
|
2695
|
+
canvas.addEventListener("contextmenu", onContextMenu2);
|
|
2513
2696
|
keyTarget.addEventListener("keydown", onKeyDown2);
|
|
2514
2697
|
return {
|
|
2515
2698
|
cleanup: () => {
|
|
@@ -2517,10 +2700,48 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2517
2700
|
canvas.removeEventListener("pointerdown", onPointerDown2);
|
|
2518
2701
|
canvas.removeEventListener("pointerup", onPointerUp2);
|
|
2519
2702
|
canvas.removeEventListener("pointermove", onPointerMove2);
|
|
2703
|
+
canvas.removeEventListener("contextmenu", onContextMenu2);
|
|
2520
2704
|
keyTarget.removeEventListener("keydown", onKeyDown2);
|
|
2521
2705
|
}
|
|
2522
2706
|
};
|
|
2523
2707
|
}
|
|
2708
|
+
function findNearestEdge(clientX, clientY, rect, camera, edgeData) {
|
|
2709
|
+
const { links, edgeNodeIndices, edgeLinkIndices, positions } = edgeData;
|
|
2710
|
+
const screenX = clientX - rect.left;
|
|
2711
|
+
const screenY = clientY - rect.top;
|
|
2712
|
+
const w = rect.width;
|
|
2713
|
+
const h = rect.height;
|
|
2714
|
+
let bestDist = EDGE_HIT_THRESHOLD;
|
|
2715
|
+
let bestIdx = -1;
|
|
2716
|
+
const tmpVec = new THREE__namespace.Vector3();
|
|
2717
|
+
for (let i = 0; i < edgeNodeIndices.length; i++) {
|
|
2718
|
+
const [si, ti] = edgeNodeIndices[i];
|
|
2719
|
+
tmpVec.set(positions[si * 3], positions[si * 3 + 1], positions[si * 3 + 2]);
|
|
2720
|
+
tmpVec.project(camera);
|
|
2721
|
+
const sx1 = (tmpVec.x * 0.5 + 0.5) * w;
|
|
2722
|
+
const sy1 = (-tmpVec.y * 0.5 + 0.5) * h;
|
|
2723
|
+
tmpVec.set(positions[ti * 3], positions[ti * 3 + 1], positions[ti * 3 + 2]);
|
|
2724
|
+
tmpVec.project(camera);
|
|
2725
|
+
const sx2 = (tmpVec.x * 0.5 + 0.5) * w;
|
|
2726
|
+
const sy2 = (-tmpVec.y * 0.5 + 0.5) * h;
|
|
2727
|
+
const dist = pointToSegmentDist(screenX, screenY, sx1, sy1, sx2, sy2);
|
|
2728
|
+
if (dist < bestDist) {
|
|
2729
|
+
bestDist = dist;
|
|
2730
|
+
bestIdx = i;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
if (bestIdx === -1) return null;
|
|
2734
|
+
return { link: links[edgeLinkIndices[bestIdx]], index: bestIdx };
|
|
2735
|
+
}
|
|
2736
|
+
function pointToSegmentDist(px, py, ax, ay, bx, by) {
|
|
2737
|
+
const dx = bx - ax;
|
|
2738
|
+
const dy = by - ay;
|
|
2739
|
+
const lenSq = dx * dx + dy * dy;
|
|
2740
|
+
if (lenSq < 1e-4) return Math.hypot(px - ax, py - ay);
|
|
2741
|
+
let t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
|
|
2742
|
+
t = Math.max(0, Math.min(1, t));
|
|
2743
|
+
return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
|
|
2744
|
+
}
|
|
2524
2745
|
function disposeObject(obj) {
|
|
2525
2746
|
if (!obj) return;
|
|
2526
2747
|
const mesh = obj;
|
|
@@ -2542,6 +2763,13 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2542
2763
|
onNodeClick,
|
|
2543
2764
|
onNodeDoubleClick,
|
|
2544
2765
|
onNodeHover,
|
|
2766
|
+
onContextMenu: onContextMenu2,
|
|
2767
|
+
onLinkClick,
|
|
2768
|
+
onLinkHover,
|
|
2769
|
+
onNodeDrag,
|
|
2770
|
+
onNodeDragEnd,
|
|
2771
|
+
onLayoutSettled,
|
|
2772
|
+
onLayoutTick,
|
|
2545
2773
|
selectedNodeId = null,
|
|
2546
2774
|
highlightHops = 3,
|
|
2547
2775
|
theme: themeProp,
|
|
@@ -2574,13 +2802,28 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2574
2802
|
const onNodeClickRef = react.useRef(onNodeClick);
|
|
2575
2803
|
const onNodeDoubleClickRef = react.useRef(onNodeDoubleClick);
|
|
2576
2804
|
const onNodeHoverRef = react.useRef(onNodeHover);
|
|
2805
|
+
const onContextMenuRef = react.useRef(onContextMenu2);
|
|
2806
|
+
const onLinkClickRef = react.useRef(onLinkClick);
|
|
2807
|
+
const onLinkHoverRef = react.useRef(onLinkHover);
|
|
2808
|
+
const onNodeDragRef = react.useRef(onNodeDrag);
|
|
2809
|
+
const onNodeDragEndRef = react.useRef(onNodeDragEnd);
|
|
2810
|
+
const onLayoutSettledRef = react.useRef(onLayoutSettled);
|
|
2811
|
+
const onLayoutTickRef = react.useRef(onLayoutTick);
|
|
2577
2812
|
const styleRef = react.useRef(resolvedStyle);
|
|
2578
2813
|
const themeRef = react.useRef(resolvedTheme);
|
|
2579
2814
|
const selectedNodeIdRef = react.useRef(selectedNodeId);
|
|
2580
2815
|
const labelFormatterRef = react.useRef(labelFormatter);
|
|
2816
|
+
const highlightSetRef = react.useRef(null);
|
|
2581
2817
|
onNodeClickRef.current = onNodeClick;
|
|
2582
2818
|
onNodeDoubleClickRef.current = onNodeDoubleClick;
|
|
2583
2819
|
onNodeHoverRef.current = onNodeHover;
|
|
2820
|
+
onContextMenuRef.current = onContextMenu2;
|
|
2821
|
+
onLinkClickRef.current = onLinkClick;
|
|
2822
|
+
onLinkHoverRef.current = onLinkHover;
|
|
2823
|
+
onNodeDragRef.current = onNodeDrag;
|
|
2824
|
+
onNodeDragEndRef.current = onNodeDragEnd;
|
|
2825
|
+
onLayoutSettledRef.current = onLayoutSettled;
|
|
2826
|
+
onLayoutTickRef.current = onLayoutTick;
|
|
2584
2827
|
styleRef.current = resolvedStyle;
|
|
2585
2828
|
themeRef.current = resolvedTheme;
|
|
2586
2829
|
selectedNodeIdRef.current = selectedNodeId;
|
|
@@ -2646,7 +2889,8 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2646
2889
|
styleRef.current.labelScale,
|
|
2647
2890
|
styleRef.current.labelThreshold,
|
|
2648
2891
|
styleRef.current.maxLabels,
|
|
2649
|
-
labelFormatterRef.current
|
|
2892
|
+
labelFormatterRef.current,
|
|
2893
|
+
highlightSetRef.current
|
|
2650
2894
|
);
|
|
2651
2895
|
});
|
|
2652
2896
|
const resizeObserver = setupResize(container, state);
|
|
@@ -2671,9 +2915,52 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2671
2915
|
onNodeHover: (node) => {
|
|
2672
2916
|
var _a;
|
|
2673
2917
|
return (_a = onNodeHoverRef.current) == null ? void 0 : _a.call(onNodeHoverRef, node);
|
|
2918
|
+
},
|
|
2919
|
+
onContextMenu: (node, pos) => {
|
|
2920
|
+
var _a;
|
|
2921
|
+
return (_a = onContextMenuRef.current) == null ? void 0 : _a.call(onContextMenuRef, node, pos);
|
|
2922
|
+
},
|
|
2923
|
+
onLinkClick: (link) => {
|
|
2924
|
+
var _a;
|
|
2925
|
+
return (_a = onLinkClickRef.current) == null ? void 0 : _a.call(onLinkClickRef, link);
|
|
2926
|
+
},
|
|
2927
|
+
onLinkHover: (link) => {
|
|
2928
|
+
var _a;
|
|
2929
|
+
return (_a = onLinkHoverRef.current) == null ? void 0 : _a.call(onLinkHoverRef, link);
|
|
2930
|
+
},
|
|
2931
|
+
onNodeDrag: (node, pos) => {
|
|
2932
|
+
var _a;
|
|
2933
|
+
(_a = onNodeDragRef.current) == null ? void 0 : _a.call(onNodeDragRef, node, pos);
|
|
2934
|
+
const d = dataRef.current;
|
|
2935
|
+
const idx = d.nodeIdToIndex.get(node.id);
|
|
2936
|
+
if (idx !== void 0 && d.positions) {
|
|
2937
|
+
d.positions[idx * 3] = pos.x;
|
|
2938
|
+
d.positions[idx * 3 + 1] = pos.y;
|
|
2939
|
+
d.positions[idx * 3 + 2] = pos.z;
|
|
2940
|
+
node.x = pos.x;
|
|
2941
|
+
node.y = pos.y;
|
|
2942
|
+
node.z = pos.z;
|
|
2943
|
+
const gObj = graphObjRef.current;
|
|
2944
|
+
if (gObj && d.scales) {
|
|
2945
|
+
updateNodePositions(gObj.nodesMesh, d.positions, d.scales, d.nodes.length);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
},
|
|
2949
|
+
onNodeDragEnd: (node, pos) => {
|
|
2950
|
+
var _a;
|
|
2951
|
+
return (_a = onNodeDragEndRef.current) == null ? void 0 : _a.call(onNodeDragEndRef, node, pos);
|
|
2674
2952
|
}
|
|
2675
2953
|
},
|
|
2676
|
-
container
|
|
2954
|
+
container,
|
|
2955
|
+
() => {
|
|
2956
|
+
const d = dataRef.current;
|
|
2957
|
+
return {
|
|
2958
|
+
links: d.links,
|
|
2959
|
+
edgeNodeIndices: d.edgeNodeIndices,
|
|
2960
|
+
edgeLinkIndices: d.edgeLinkIndices,
|
|
2961
|
+
positions: d.positions
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2677
2964
|
);
|
|
2678
2965
|
return () => {
|
|
2679
2966
|
cancelAnim();
|
|
@@ -2813,6 +3100,7 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2813
3100
|
console.error("Graphier: Layout worker error:", err);
|
|
2814
3101
|
};
|
|
2815
3102
|
worker.onmessage = (e) => {
|
|
3103
|
+
var _a, _b;
|
|
2816
3104
|
const msg = e.data;
|
|
2817
3105
|
if (msg.type === "positions") {
|
|
2818
3106
|
const positions = new Float32Array(msg.positions);
|
|
@@ -2840,9 +3128,11 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2840
3128
|
);
|
|
2841
3129
|
}
|
|
2842
3130
|
}
|
|
3131
|
+
(_a = onLayoutTickRef.current) == null ? void 0 : _a.call(onLayoutTickRef, msg.alpha);
|
|
2843
3132
|
}
|
|
2844
3133
|
if (msg.type === "settled") {
|
|
2845
3134
|
dataRef.current.settled = true;
|
|
3135
|
+
(_b = onLayoutSettledRef.current) == null ? void 0 : _b.call(onLayoutSettledRef);
|
|
2846
3136
|
}
|
|
2847
3137
|
};
|
|
2848
3138
|
const layoutParams = resolveLayoutParams(nc, layoutProp);
|
|
@@ -2887,6 +3177,7 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2887
3177
|
const d = dataRef.current;
|
|
2888
3178
|
const theme = themeRef.current;
|
|
2889
3179
|
const style = styleRef.current;
|
|
3180
|
+
highlightSetRef.current = highlightSet;
|
|
2890
3181
|
applyNodeHighlight(
|
|
2891
3182
|
gObj.nodesMesh,
|
|
2892
3183
|
d.nodes,
|
|
@@ -2904,6 +3195,18 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2904
3195
|
d.nodes.length,
|
|
2905
3196
|
theme
|
|
2906
3197
|
);
|
|
3198
|
+
if (sceneState.bloomPass) {
|
|
3199
|
+
const style2 = styleRef.current;
|
|
3200
|
+
if (highlightSet) {
|
|
3201
|
+
sceneState.bloomPass.strength = style2.bloomStrength * 1.8;
|
|
3202
|
+
sceneState.bloomPass.radius = style2.bloomRadius * 1.4;
|
|
3203
|
+
sceneState.bloomPass.threshold = 0.25;
|
|
3204
|
+
} else {
|
|
3205
|
+
sceneState.bloomPass.strength = style2.bloomStrength;
|
|
3206
|
+
sceneState.bloomPass.radius = style2.bloomRadius;
|
|
3207
|
+
sceneState.bloomPass.threshold = style2.bloomThreshold;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
2907
3210
|
const selectedNode = selectedNodeId ? d.nodes.find((n) => n.id === selectedNodeId) : null;
|
|
2908
3211
|
updateSelectionRing(
|
|
2909
3212
|
sceneState.selectionRing,
|
|
@@ -3051,13 +3354,11 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
3051
3354
|
var _a;
|
|
3052
3355
|
return ((_a = sceneRef.current) == null ? void 0 : _a.camera) ?? null;
|
|
3053
3356
|
},
|
|
3054
|
-
|
|
3357
|
+
captureScreenshot() {
|
|
3055
3358
|
const s = sceneRef.current;
|
|
3056
3359
|
if (!s) return null;
|
|
3057
3360
|
s.renderer.render(s.scene, s.camera);
|
|
3058
|
-
return
|
|
3059
|
-
s.renderer.domElement.toBlob(resolve, "image/png");
|
|
3060
|
-
});
|
|
3361
|
+
return s.renderer.domElement.toDataURL("image/png");
|
|
3061
3362
|
}
|
|
3062
3363
|
}));
|
|
3063
3364
|
return /* @__PURE__ */ jsxRuntime.jsx(
|