@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.js
CHANGED
|
@@ -1703,27 +1703,31 @@ function applyNodeHighlight(nodesMesh, nodes, selectedNodeId, highlightSet, them
|
|
|
1703
1703
|
} else if (node.id === selectedNodeId) {
|
|
1704
1704
|
tmpColor.set(brightColor);
|
|
1705
1705
|
brightTmp.setRGB(1, 1, 1);
|
|
1706
|
-
tmpColor.lerp(brightTmp, 0.
|
|
1706
|
+
tmpColor.lerp(brightTmp, 0.65);
|
|
1707
|
+
tmpColor.multiplyScalar(1.4);
|
|
1708
|
+
tmpColor.r = Math.min(tmpColor.r, 2);
|
|
1709
|
+
tmpColor.g = Math.min(tmpColor.g, 2);
|
|
1710
|
+
tmpColor.b = Math.min(tmpColor.b, 2);
|
|
1707
1711
|
} else if (highlightSet.has(node.id)) {
|
|
1708
1712
|
const hop = highlightSet.get(node.id);
|
|
1709
1713
|
if (hop === 1) {
|
|
1710
|
-
tmpColor.set(brightColor);
|
|
1714
|
+
tmpColor.set(brightColor).multiplyScalar(1.2);
|
|
1711
1715
|
} else if (hop === 2) {
|
|
1712
1716
|
tmpColor.set(baseColor);
|
|
1713
1717
|
brightTmp.set(brightColor);
|
|
1714
|
-
tmpColor.lerp(brightTmp, 0.
|
|
1718
|
+
tmpColor.lerp(brightTmp, 0.5);
|
|
1715
1719
|
} else {
|
|
1716
|
-
tmpColor.set(baseColor).multiplyScalar(0.
|
|
1720
|
+
tmpColor.set(baseColor).multiplyScalar(0.6);
|
|
1717
1721
|
}
|
|
1718
1722
|
} else {
|
|
1719
|
-
tmpColor.set(baseColor).multiplyScalar(0.
|
|
1723
|
+
tmpColor.set(baseColor).multiplyScalar(0.04);
|
|
1720
1724
|
}
|
|
1721
1725
|
nodesMesh.setColorAt(i, tmpColor);
|
|
1722
1726
|
}
|
|
1723
1727
|
if (nodesMesh.instanceColor) nodesMesh.instanceColor.needsUpdate = true;
|
|
1724
1728
|
const mat = nodesMesh.material;
|
|
1725
1729
|
if ((_a = mat.uniforms) == null ? void 0 : _a.uGlowIntensity) {
|
|
1726
|
-
mat.uniforms.uGlowIntensity.value = highlightSet ? 1.
|
|
1730
|
+
mat.uniforms.uGlowIntensity.value = highlightSet ? 1.5 : 1;
|
|
1727
1731
|
}
|
|
1728
1732
|
}
|
|
1729
1733
|
function applyEdgeHighlight(edgesMesh, links, edgeNodeIndices, edgeLinkIndices, highlightSet, edgeOpacity, nodeCount, theme) {
|
|
@@ -1751,7 +1755,7 @@ function applyEdgeHighlight(edgesMesh, links, edgeNodeIndices, edgeLinkIndices,
|
|
|
1751
1755
|
tmpColor.multiplyScalar(1.2);
|
|
1752
1756
|
}
|
|
1753
1757
|
} else {
|
|
1754
|
-
tmpColor.setRGB(0.
|
|
1758
|
+
tmpColor.setRGB(0.01, 0.01, 0.02);
|
|
1755
1759
|
}
|
|
1756
1760
|
colorArr[i * 6 + 0] = tmpColor.r;
|
|
1757
1761
|
colorArr[i * 6 + 1] = tmpColor.g;
|
|
@@ -1838,6 +1842,9 @@ function createLabelSystem(maxLabels) {
|
|
|
1838
1842
|
}
|
|
1839
1843
|
function defaultLabelText$1(node) {
|
|
1840
1844
|
const label = node.label ?? node.id ?? "";
|
|
1845
|
+
if (node.type === "repo" && label.includes("/")) {
|
|
1846
|
+
return label.split("/").pop();
|
|
1847
|
+
}
|
|
1841
1848
|
return label.length > 30 ? label.substring(0, 27) + "…" : label;
|
|
1842
1849
|
}
|
|
1843
1850
|
function getOrCreateTexture(cache, nodeId, text, color) {
|
|
@@ -1876,7 +1883,7 @@ function getOrCreateTexture(cache, nodeId, text, color) {
|
|
|
1876
1883
|
}
|
|
1877
1884
|
return entry;
|
|
1878
1885
|
}
|
|
1879
|
-
function updateLabels(state, nodes, positions, scales, camera, theme, showLabels, labelScale, labelThreshold, maxLabels, labelFormatter) {
|
|
1886
|
+
function updateLabels(state, nodes, positions, scales, camera, theme, showLabels, labelScale, labelThreshold, maxLabels, labelFormatter, highlightSet) {
|
|
1880
1887
|
if (!positions || nodes.length === 0 || !showLabels) {
|
|
1881
1888
|
for (const sp of state.sprites) sp.visible = false;
|
|
1882
1889
|
return;
|
|
@@ -1890,6 +1897,7 @@ function updateLabels(state, nodes, positions, scales, camera, theme, showLabels
|
|
|
1890
1897
|
const maxDistSq = maxDist * maxDist;
|
|
1891
1898
|
const candidates = [];
|
|
1892
1899
|
for (let i = 0; i < nc; i++) {
|
|
1900
|
+
if (highlightSet && !highlightSet.has(nodes[i].id)) continue;
|
|
1893
1901
|
const dx = positions[i * 3] - camPos.x;
|
|
1894
1902
|
const dy = positions[i * 3 + 1] - camPos.y;
|
|
1895
1903
|
const dz = positions[i * 3 + 2] - camPos.z;
|
|
@@ -1935,58 +1943,48 @@ function disposeLabelSystem(state) {
|
|
|
1935
1943
|
}
|
|
1936
1944
|
state.textureCache.clear();
|
|
1937
1945
|
}
|
|
1938
|
-
const
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
1941
|
-
const
|
|
1942
|
-
const
|
|
1946
|
+
const ORBIT_ACCEL = 12e-4;
|
|
1947
|
+
const ORBIT_MAX_SPEED = 0.06;
|
|
1948
|
+
const ORBIT_DAMPING = 0.88;
|
|
1949
|
+
const ORBIT_ZOOM_FACTOR = 0.015;
|
|
1950
|
+
const ORBIT_DEAD_ZONE = 1e-4;
|
|
1951
|
+
const FLY_ACCEL = 6e-3;
|
|
1952
|
+
const FLY_FRICTION = 0.92;
|
|
1953
|
+
const FLY_MAX_THRUST = 0.18;
|
|
1954
|
+
const FLY_TURN_RATE = 8e-3;
|
|
1955
|
+
const FLY_DEAD_ZONE = 2e-4;
|
|
1943
1956
|
const _offset = new THREE.Vector3();
|
|
1944
1957
|
const _right = new THREE.Vector3();
|
|
1945
1958
|
const _q = new THREE.Quaternion();
|
|
1946
|
-
|
|
1947
|
-
|
|
1959
|
+
const _dir = new THREE.Vector3();
|
|
1960
|
+
function clamp(v, min, max) {
|
|
1961
|
+
return v < min ? min : v > max ? max : v;
|
|
1962
|
+
}
|
|
1963
|
+
function createOrbitUpdate(camera, controls, keys) {
|
|
1948
1964
|
const vel = { zoom: 0, azimuth: 0, polar: 0 };
|
|
1949
|
-
function
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
if (
|
|
1953
|
-
keys[
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
vel.
|
|
1961
|
-
vel.
|
|
1962
|
-
vel.
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
container.addEventListener("keyup", onKeyUp);
|
|
1966
|
-
container.addEventListener("blur", onBlur);
|
|
1967
|
-
function update() {
|
|
1968
|
-
if (keys["z"] || keys["Z"]) vel.zoom -= ACCEL;
|
|
1969
|
-
if (keys["x"] || keys["X"]) vel.zoom += ACCEL;
|
|
1970
|
-
if (keys["ArrowLeft"]) vel.azimuth -= ACCEL;
|
|
1971
|
-
if (keys["ArrowRight"]) vel.azimuth += ACCEL;
|
|
1972
|
-
if (keys["ArrowUp"]) vel.polar -= ACCEL;
|
|
1973
|
-
if (keys["ArrowDown"]) vel.polar += ACCEL;
|
|
1974
|
-
vel.zoom = clamp(vel.zoom, -MAX_SPEED, MAX_SPEED);
|
|
1975
|
-
vel.azimuth = clamp(vel.azimuth, -MAX_SPEED, MAX_SPEED);
|
|
1976
|
-
vel.polar = clamp(vel.polar, -MAX_SPEED, MAX_SPEED);
|
|
1977
|
-
vel.zoom *= DAMPING;
|
|
1978
|
-
vel.azimuth *= DAMPING;
|
|
1979
|
-
vel.polar *= DAMPING;
|
|
1980
|
-
if (Math.abs(vel.zoom) < DEAD_ZONE) vel.zoom = 0;
|
|
1981
|
-
if (Math.abs(vel.azimuth) < DEAD_ZONE) vel.azimuth = 0;
|
|
1982
|
-
if (Math.abs(vel.polar) < DEAD_ZONE) vel.polar = 0;
|
|
1965
|
+
return function update() {
|
|
1966
|
+
if (keys["z"] || keys["Z"]) vel.zoom -= ORBIT_ACCEL;
|
|
1967
|
+
if (keys["x"] || keys["X"]) vel.zoom += ORBIT_ACCEL;
|
|
1968
|
+
if (keys["ArrowLeft"]) vel.azimuth -= ORBIT_ACCEL;
|
|
1969
|
+
if (keys["ArrowRight"]) vel.azimuth += ORBIT_ACCEL;
|
|
1970
|
+
if (keys["ArrowUp"]) vel.polar -= ORBIT_ACCEL;
|
|
1971
|
+
if (keys["ArrowDown"]) vel.polar += ORBIT_ACCEL;
|
|
1972
|
+
vel.zoom = clamp(vel.zoom, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
|
|
1973
|
+
vel.azimuth = clamp(vel.azimuth, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
|
|
1974
|
+
vel.polar = clamp(vel.polar, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
|
|
1975
|
+
vel.zoom *= ORBIT_DAMPING;
|
|
1976
|
+
vel.azimuth *= ORBIT_DAMPING;
|
|
1977
|
+
vel.polar *= ORBIT_DAMPING;
|
|
1978
|
+
if (Math.abs(vel.zoom) < ORBIT_DEAD_ZONE) vel.zoom = 0;
|
|
1979
|
+
if (Math.abs(vel.azimuth) < ORBIT_DEAD_ZONE) vel.azimuth = 0;
|
|
1980
|
+
if (Math.abs(vel.polar) < ORBIT_DEAD_ZONE) vel.polar = 0;
|
|
1983
1981
|
const hasMotion = vel.zoom !== 0 || vel.azimuth !== 0 || vel.polar !== 0;
|
|
1984
1982
|
if (!hasMotion) return false;
|
|
1985
1983
|
const target = controls.target;
|
|
1986
1984
|
_offset.copy(camera.position).sub(target);
|
|
1987
1985
|
let radius = _offset.length();
|
|
1988
1986
|
if (vel.zoom !== 0) {
|
|
1989
|
-
radius *= 1 + vel.zoom *
|
|
1987
|
+
radius *= 1 + vel.zoom * ORBIT_ZOOM_FACTOR * radius * 0.01;
|
|
1990
1988
|
radius = Math.max(controls.minDistance, Math.min(controls.maxDistance, radius));
|
|
1991
1989
|
}
|
|
1992
1990
|
if (vel.azimuth !== 0) {
|
|
@@ -2007,16 +2005,72 @@ function setupKeyboardControls(camera, controls, container) {
|
|
|
2007
2005
|
camera.lookAt(target);
|
|
2008
2006
|
controls.target.copy(target);
|
|
2009
2007
|
return true;
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
function createFlyUpdate(camera, controls, keys) {
|
|
2011
|
+
const vel = { thrust: 0, yaw: 0, pitch: 0 };
|
|
2012
|
+
return function update() {
|
|
2013
|
+
if (keys["z"] || keys["Z"]) vel.thrust += FLY_ACCEL;
|
|
2014
|
+
if (keys["x"] || keys["X"]) vel.thrust -= FLY_ACCEL;
|
|
2015
|
+
if (keys["a"] || keys["A"] || keys["ArrowLeft"]) vel.yaw += FLY_TURN_RATE;
|
|
2016
|
+
if (keys["d"] || keys["D"] || keys["ArrowRight"]) vel.yaw -= FLY_TURN_RATE;
|
|
2017
|
+
if (keys["w"] || keys["W"] || keys["ArrowUp"]) vel.pitch += FLY_TURN_RATE;
|
|
2018
|
+
if (keys["s"] || keys["S"] || keys["ArrowDown"]) vel.pitch -= FLY_TURN_RATE;
|
|
2019
|
+
const lookDist = camera.position.distanceTo(controls.target);
|
|
2020
|
+
const distScale = Math.max(0.3, lookDist * 3e-3);
|
|
2021
|
+
vel.thrust = clamp(vel.thrust, -FLY_MAX_THRUST * distScale, FLY_MAX_THRUST * distScale);
|
|
2022
|
+
vel.yaw = clamp(vel.yaw, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
|
|
2023
|
+
vel.pitch = clamp(vel.pitch, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
|
|
2024
|
+
vel.thrust *= FLY_FRICTION;
|
|
2025
|
+
vel.yaw *= FLY_FRICTION;
|
|
2026
|
+
vel.pitch *= FLY_FRICTION;
|
|
2027
|
+
if (Math.abs(vel.thrust) < FLY_DEAD_ZONE) vel.thrust = 0;
|
|
2028
|
+
if (Math.abs(vel.yaw) < FLY_DEAD_ZONE) vel.yaw = 0;
|
|
2029
|
+
if (Math.abs(vel.pitch) < FLY_DEAD_ZONE) vel.pitch = 0;
|
|
2030
|
+
const hasMotion = vel.thrust !== 0 || vel.yaw !== 0 || vel.pitch !== 0;
|
|
2031
|
+
if (!hasMotion) return false;
|
|
2032
|
+
if (vel.yaw !== 0) {
|
|
2033
|
+
_q.setFromAxisAngle(camera.up, vel.yaw);
|
|
2034
|
+
camera.quaternion.premultiply(_q);
|
|
2035
|
+
}
|
|
2036
|
+
if (vel.pitch !== 0) {
|
|
2037
|
+
_right.setFromMatrixColumn(camera.matrixWorld, 0).normalize();
|
|
2038
|
+
_q.setFromAxisAngle(_right, vel.pitch);
|
|
2039
|
+
camera.quaternion.premultiply(_q);
|
|
2040
|
+
}
|
|
2041
|
+
if (vel.thrust !== 0) {
|
|
2042
|
+
camera.getWorldDirection(_dir);
|
|
2043
|
+
_dir.multiplyScalar(vel.thrust * distScale * 100);
|
|
2044
|
+
camera.position.add(_dir);
|
|
2045
|
+
controls.target.add(_dir);
|
|
2046
|
+
}
|
|
2047
|
+
return true;
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
function setupKeyboardControls(camera, controls, container, mode = "fly") {
|
|
2051
|
+
const keys = {};
|
|
2052
|
+
function onKeyDown2(e) {
|
|
2053
|
+
var _a;
|
|
2054
|
+
const tag = (_a = e.target) == null ? void 0 : _a.tagName;
|
|
2055
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
2056
|
+
keys[e.key] = true;
|
|
2057
|
+
}
|
|
2058
|
+
function onKeyUp(e) {
|
|
2059
|
+
keys[e.key] = false;
|
|
2060
|
+
}
|
|
2061
|
+
function onBlur() {
|
|
2062
|
+
for (const k of Object.keys(keys)) keys[k] = false;
|
|
2010
2063
|
}
|
|
2064
|
+
container.addEventListener("keydown", onKeyDown2);
|
|
2065
|
+
container.addEventListener("keyup", onKeyUp);
|
|
2066
|
+
container.addEventListener("blur", onBlur);
|
|
2067
|
+
const updateFn = mode === "fly" ? createFlyUpdate(camera, controls, keys) : createOrbitUpdate(camera, controls, keys);
|
|
2011
2068
|
function cleanup() {
|
|
2012
2069
|
container.removeEventListener("keydown", onKeyDown2);
|
|
2013
2070
|
container.removeEventListener("keyup", onKeyUp);
|
|
2014
2071
|
container.removeEventListener("blur", onBlur);
|
|
2015
2072
|
}
|
|
2016
|
-
return { update, cleanup };
|
|
2017
|
-
}
|
|
2018
|
-
function clamp(v, min, max) {
|
|
2019
|
-
return v < min ? min : v > max ? max : v;
|
|
2073
|
+
return { update: updateFn, cleanup };
|
|
2020
2074
|
}
|
|
2021
2075
|
function createScene(container, backgroundColor, style, rendererConfig) {
|
|
2022
2076
|
const scene = new THREE.Scene();
|
|
@@ -2053,7 +2107,8 @@ function createScene(container, backgroundColor, style, rendererConfig) {
|
|
|
2053
2107
|
scene.add(selectionRing);
|
|
2054
2108
|
const labels = createLabelSystem(style.maxLabels);
|
|
2055
2109
|
scene.add(labels.group);
|
|
2056
|
-
const
|
|
2110
|
+
const cameraMode = (rendererConfig == null ? void 0 : rendererConfig.cameraMode) ?? "fly";
|
|
2111
|
+
const keyboard = setupKeyboardControls(camera, controls, container, cameraMode);
|
|
2057
2112
|
return {
|
|
2058
2113
|
scene,
|
|
2059
2114
|
camera,
|
|
@@ -2392,21 +2447,136 @@ function zoomToFitPositions(camera, controls, positions, duration, padding) {
|
|
|
2392
2447
|
duration
|
|
2393
2448
|
);
|
|
2394
2449
|
}
|
|
2395
|
-
|
|
2450
|
+
const EDGE_HIT_THRESHOLD = 5;
|
|
2451
|
+
function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, callbacks, container, getEdgeData) {
|
|
2396
2452
|
const raycaster = new THREE.Raycaster();
|
|
2397
2453
|
const mouse = new THREE.Vector2();
|
|
2398
2454
|
let mouseDownPos = null;
|
|
2399
2455
|
let lastClickTime = 0;
|
|
2400
2456
|
let lastClickNodeId = null;
|
|
2401
2457
|
let singleClickTimer = null;
|
|
2458
|
+
let dragNode = null;
|
|
2459
|
+
let isDragging = false;
|
|
2460
|
+
const dragPlane = new THREE.Plane();
|
|
2461
|
+
const dragIntersect = new THREE.Vector3();
|
|
2402
2462
|
function onPointerDown2(e) {
|
|
2403
2463
|
mouseDownPos = { x: e.clientX, y: e.clientY };
|
|
2404
2464
|
if (container && document.activeElement !== container) {
|
|
2405
2465
|
container.focus({ preventScroll: true });
|
|
2406
2466
|
}
|
|
2467
|
+
if (callbacks.onNodeDrag) {
|
|
2468
|
+
const nodesMesh = getNodesMesh();
|
|
2469
|
+
if (nodesMesh) {
|
|
2470
|
+
const rect = canvas.getBoundingClientRect();
|
|
2471
|
+
mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
2472
|
+
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
2473
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2474
|
+
const hits = raycaster.intersectObject(nodesMesh);
|
|
2475
|
+
if (hits.length > 0 && hits[0].instanceId != null) {
|
|
2476
|
+
const nodes = getNodes();
|
|
2477
|
+
dragNode = nodes[hits[0].instanceId] ?? null;
|
|
2478
|
+
hits[0].instanceId;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
function onPointerMove2(e) {
|
|
2484
|
+
var _a, _b, _c;
|
|
2485
|
+
if (dragNode && mouseDownPos && callbacks.onNodeDrag) {
|
|
2486
|
+
const dx = Math.abs(e.clientX - mouseDownPos.x);
|
|
2487
|
+
const dy = Math.abs(e.clientY - mouseDownPos.y);
|
|
2488
|
+
if (dx + dy > 5) {
|
|
2489
|
+
isDragging = true;
|
|
2490
|
+
controls.enabled = false;
|
|
2491
|
+
const rect2 = canvas.getBoundingClientRect();
|
|
2492
|
+
mouse.x = (e.clientX - rect2.left) / rect2.width * 2 - 1;
|
|
2493
|
+
mouse.y = -((e.clientY - rect2.top) / rect2.height) * 2 + 1;
|
|
2494
|
+
const nodePos = new THREE.Vector3(
|
|
2495
|
+
dragNode.x ?? 0,
|
|
2496
|
+
dragNode.y ?? 0,
|
|
2497
|
+
dragNode.z ?? 0
|
|
2498
|
+
);
|
|
2499
|
+
dragPlane.setFromNormalAndCoplanarPoint(
|
|
2500
|
+
camera.getWorldDirection(new THREE.Vector3()).negate(),
|
|
2501
|
+
nodePos
|
|
2502
|
+
);
|
|
2503
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2504
|
+
if (raycaster.ray.intersectPlane(dragPlane, dragIntersect)) {
|
|
2505
|
+
callbacks.onNodeDrag(dragNode, {
|
|
2506
|
+
x: dragIntersect.x,
|
|
2507
|
+
y: dragIntersect.y,
|
|
2508
|
+
z: dragIntersect.z
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
if (!callbacks.onNodeHover && !callbacks.onLinkHover) return;
|
|
2515
|
+
const nodesMesh = getNodesMesh();
|
|
2516
|
+
if (!nodesMesh) return;
|
|
2517
|
+
const rect = canvas.getBoundingClientRect();
|
|
2518
|
+
mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
2519
|
+
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
2520
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2521
|
+
const hits = raycaster.intersectObject(nodesMesh);
|
|
2522
|
+
if (hits.length > 0 && hits[0].instanceId != null) {
|
|
2523
|
+
const nodes = getNodes();
|
|
2524
|
+
const node = nodes[hits[0].instanceId];
|
|
2525
|
+
if (node && node.id !== lastHoveredId) {
|
|
2526
|
+
lastHoveredId = node.id;
|
|
2527
|
+
lastHoveredLinkIdx = -1;
|
|
2528
|
+
canvas.style.cursor = "pointer";
|
|
2529
|
+
(_a = callbacks.onNodeHover) == null ? void 0 : _a.call(callbacks, node);
|
|
2530
|
+
(_b = callbacks.onLinkHover) == null ? void 0 : _b.call(callbacks, null);
|
|
2531
|
+
}
|
|
2532
|
+
} else {
|
|
2533
|
+
if (lastHoveredId !== null) {
|
|
2534
|
+
lastHoveredId = null;
|
|
2535
|
+
canvas.style.cursor = "default";
|
|
2536
|
+
(_c = callbacks.onNodeHover) == null ? void 0 : _c.call(callbacks, null);
|
|
2537
|
+
}
|
|
2538
|
+
if (callbacks.onLinkHover && getEdgeData) {
|
|
2539
|
+
const edgeData = getEdgeData();
|
|
2540
|
+
if (edgeData == null ? void 0 : edgeData.positions) {
|
|
2541
|
+
const hitLink = findNearestEdge(
|
|
2542
|
+
e.clientX,
|
|
2543
|
+
e.clientY,
|
|
2544
|
+
rect,
|
|
2545
|
+
camera,
|
|
2546
|
+
edgeData
|
|
2547
|
+
);
|
|
2548
|
+
if (hitLink) {
|
|
2549
|
+
if (lastHoveredLinkIdx !== hitLink.index) {
|
|
2550
|
+
lastHoveredLinkIdx = hitLink.index;
|
|
2551
|
+
canvas.style.cursor = "pointer";
|
|
2552
|
+
callbacks.onLinkHover(hitLink.link);
|
|
2553
|
+
}
|
|
2554
|
+
} else if (lastHoveredLinkIdx !== -1) {
|
|
2555
|
+
lastHoveredLinkIdx = -1;
|
|
2556
|
+
canvas.style.cursor = "default";
|
|
2557
|
+
callbacks.onLinkHover(null);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2407
2562
|
}
|
|
2408
2563
|
function onPointerUp2(e) {
|
|
2409
2564
|
var _a;
|
|
2565
|
+
if (isDragging && dragNode && callbacks.onNodeDragEnd) {
|
|
2566
|
+
callbacks.onNodeDragEnd(dragNode, {
|
|
2567
|
+
x: dragNode.x ?? 0,
|
|
2568
|
+
y: dragNode.y ?? 0,
|
|
2569
|
+
z: dragNode.z ?? 0
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
if (isDragging) {
|
|
2573
|
+
isDragging = false;
|
|
2574
|
+
dragNode = null;
|
|
2575
|
+
controls.enabled = true;
|
|
2576
|
+
mouseDownPos = null;
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
dragNode = null;
|
|
2410
2580
|
if (!mouseDownPos) return;
|
|
2411
2581
|
const dx = Math.abs(e.clientX - mouseDownPos.x);
|
|
2412
2582
|
const dy = Math.abs(e.clientY - mouseDownPos.y);
|
|
@@ -2454,6 +2624,22 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2454
2624
|
callbacks.onNodeClick(node);
|
|
2455
2625
|
}, 250);
|
|
2456
2626
|
} else {
|
|
2627
|
+
if (callbacks.onLinkClick && getEdgeData) {
|
|
2628
|
+
const edgeData = getEdgeData();
|
|
2629
|
+
if (edgeData == null ? void 0 : edgeData.positions) {
|
|
2630
|
+
const hitLink = findNearestEdge(
|
|
2631
|
+
e.clientX,
|
|
2632
|
+
e.clientY,
|
|
2633
|
+
rect,
|
|
2634
|
+
camera,
|
|
2635
|
+
edgeData
|
|
2636
|
+
);
|
|
2637
|
+
if (hitLink) {
|
|
2638
|
+
callbacks.onLinkClick(hitLink.link);
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2457
2643
|
if (singleClickTimer) clearTimeout(singleClickTimer);
|
|
2458
2644
|
singleClickTimer = null;
|
|
2459
2645
|
lastClickTime = 0;
|
|
@@ -2461,13 +2647,9 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2461
2647
|
callbacks.onNodeClick(null);
|
|
2462
2648
|
}
|
|
2463
2649
|
}
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
}
|
|
2468
|
-
let lastHoveredId = null;
|
|
2469
|
-
function onPointerMove2(e) {
|
|
2470
|
-
if (!callbacks.onNodeHover) return;
|
|
2650
|
+
function onContextMenu2(e) {
|
|
2651
|
+
if (!callbacks.onContextMenu) return;
|
|
2652
|
+
e.preventDefault();
|
|
2471
2653
|
const nodesMesh = getNodesMesh();
|
|
2472
2654
|
if (!nodesMesh) return;
|
|
2473
2655
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -2478,20 +2660,21 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2478
2660
|
if (hits.length > 0 && hits[0].instanceId != null) {
|
|
2479
2661
|
const nodes = getNodes();
|
|
2480
2662
|
const node = nodes[hits[0].instanceId];
|
|
2481
|
-
if (node
|
|
2482
|
-
|
|
2483
|
-
canvas.style.cursor = "pointer";
|
|
2484
|
-
callbacks.onNodeHover(node);
|
|
2663
|
+
if (node) {
|
|
2664
|
+
callbacks.onContextMenu(node, { x: e.clientX, y: e.clientY });
|
|
2485
2665
|
}
|
|
2486
|
-
} else if (lastHoveredId !== null) {
|
|
2487
|
-
lastHoveredId = null;
|
|
2488
|
-
canvas.style.cursor = "default";
|
|
2489
|
-
callbacks.onNodeHover(null);
|
|
2490
2666
|
}
|
|
2491
2667
|
}
|
|
2668
|
+
const keyTarget = container ?? window;
|
|
2669
|
+
function onKeyDown2(e) {
|
|
2670
|
+
if (e.key === "Escape") callbacks.onNodeClick(null);
|
|
2671
|
+
}
|
|
2672
|
+
let lastHoveredId = null;
|
|
2673
|
+
let lastHoveredLinkIdx = -1;
|
|
2492
2674
|
canvas.addEventListener("pointerdown", onPointerDown2);
|
|
2493
2675
|
canvas.addEventListener("pointerup", onPointerUp2);
|
|
2494
2676
|
canvas.addEventListener("pointermove", onPointerMove2);
|
|
2677
|
+
canvas.addEventListener("contextmenu", onContextMenu2);
|
|
2495
2678
|
keyTarget.addEventListener("keydown", onKeyDown2);
|
|
2496
2679
|
return {
|
|
2497
2680
|
cleanup: () => {
|
|
@@ -2499,10 +2682,48 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2499
2682
|
canvas.removeEventListener("pointerdown", onPointerDown2);
|
|
2500
2683
|
canvas.removeEventListener("pointerup", onPointerUp2);
|
|
2501
2684
|
canvas.removeEventListener("pointermove", onPointerMove2);
|
|
2685
|
+
canvas.removeEventListener("contextmenu", onContextMenu2);
|
|
2502
2686
|
keyTarget.removeEventListener("keydown", onKeyDown2);
|
|
2503
2687
|
}
|
|
2504
2688
|
};
|
|
2505
2689
|
}
|
|
2690
|
+
function findNearestEdge(clientX, clientY, rect, camera, edgeData) {
|
|
2691
|
+
const { links, edgeNodeIndices, edgeLinkIndices, positions } = edgeData;
|
|
2692
|
+
const screenX = clientX - rect.left;
|
|
2693
|
+
const screenY = clientY - rect.top;
|
|
2694
|
+
const w = rect.width;
|
|
2695
|
+
const h = rect.height;
|
|
2696
|
+
let bestDist = EDGE_HIT_THRESHOLD;
|
|
2697
|
+
let bestIdx = -1;
|
|
2698
|
+
const tmpVec = new THREE.Vector3();
|
|
2699
|
+
for (let i = 0; i < edgeNodeIndices.length; i++) {
|
|
2700
|
+
const [si, ti] = edgeNodeIndices[i];
|
|
2701
|
+
tmpVec.set(positions[si * 3], positions[si * 3 + 1], positions[si * 3 + 2]);
|
|
2702
|
+
tmpVec.project(camera);
|
|
2703
|
+
const sx1 = (tmpVec.x * 0.5 + 0.5) * w;
|
|
2704
|
+
const sy1 = (-tmpVec.y * 0.5 + 0.5) * h;
|
|
2705
|
+
tmpVec.set(positions[ti * 3], positions[ti * 3 + 1], positions[ti * 3 + 2]);
|
|
2706
|
+
tmpVec.project(camera);
|
|
2707
|
+
const sx2 = (tmpVec.x * 0.5 + 0.5) * w;
|
|
2708
|
+
const sy2 = (-tmpVec.y * 0.5 + 0.5) * h;
|
|
2709
|
+
const dist = pointToSegmentDist(screenX, screenY, sx1, sy1, sx2, sy2);
|
|
2710
|
+
if (dist < bestDist) {
|
|
2711
|
+
bestDist = dist;
|
|
2712
|
+
bestIdx = i;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
if (bestIdx === -1) return null;
|
|
2716
|
+
return { link: links[edgeLinkIndices[bestIdx]], index: bestIdx };
|
|
2717
|
+
}
|
|
2718
|
+
function pointToSegmentDist(px, py, ax, ay, bx, by) {
|
|
2719
|
+
const dx = bx - ax;
|
|
2720
|
+
const dy = by - ay;
|
|
2721
|
+
const lenSq = dx * dx + dy * dy;
|
|
2722
|
+
if (lenSq < 1e-4) return Math.hypot(px - ax, py - ay);
|
|
2723
|
+
let t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
|
|
2724
|
+
t = Math.max(0, Math.min(1, t));
|
|
2725
|
+
return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
|
|
2726
|
+
}
|
|
2506
2727
|
function disposeObject(obj) {
|
|
2507
2728
|
if (!obj) return;
|
|
2508
2729
|
const mesh = obj;
|
|
@@ -2524,6 +2745,13 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2524
2745
|
onNodeClick,
|
|
2525
2746
|
onNodeDoubleClick,
|
|
2526
2747
|
onNodeHover,
|
|
2748
|
+
onContextMenu: onContextMenu2,
|
|
2749
|
+
onLinkClick,
|
|
2750
|
+
onLinkHover,
|
|
2751
|
+
onNodeDrag,
|
|
2752
|
+
onNodeDragEnd,
|
|
2753
|
+
onLayoutSettled,
|
|
2754
|
+
onLayoutTick,
|
|
2527
2755
|
selectedNodeId = null,
|
|
2528
2756
|
highlightHops = 3,
|
|
2529
2757
|
theme: themeProp,
|
|
@@ -2556,13 +2784,28 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2556
2784
|
const onNodeClickRef = useRef(onNodeClick);
|
|
2557
2785
|
const onNodeDoubleClickRef = useRef(onNodeDoubleClick);
|
|
2558
2786
|
const onNodeHoverRef = useRef(onNodeHover);
|
|
2787
|
+
const onContextMenuRef = useRef(onContextMenu2);
|
|
2788
|
+
const onLinkClickRef = useRef(onLinkClick);
|
|
2789
|
+
const onLinkHoverRef = useRef(onLinkHover);
|
|
2790
|
+
const onNodeDragRef = useRef(onNodeDrag);
|
|
2791
|
+
const onNodeDragEndRef = useRef(onNodeDragEnd);
|
|
2792
|
+
const onLayoutSettledRef = useRef(onLayoutSettled);
|
|
2793
|
+
const onLayoutTickRef = useRef(onLayoutTick);
|
|
2559
2794
|
const styleRef = useRef(resolvedStyle);
|
|
2560
2795
|
const themeRef = useRef(resolvedTheme);
|
|
2561
2796
|
const selectedNodeIdRef = useRef(selectedNodeId);
|
|
2562
2797
|
const labelFormatterRef = useRef(labelFormatter);
|
|
2798
|
+
const highlightSetRef = useRef(null);
|
|
2563
2799
|
onNodeClickRef.current = onNodeClick;
|
|
2564
2800
|
onNodeDoubleClickRef.current = onNodeDoubleClick;
|
|
2565
2801
|
onNodeHoverRef.current = onNodeHover;
|
|
2802
|
+
onContextMenuRef.current = onContextMenu2;
|
|
2803
|
+
onLinkClickRef.current = onLinkClick;
|
|
2804
|
+
onLinkHoverRef.current = onLinkHover;
|
|
2805
|
+
onNodeDragRef.current = onNodeDrag;
|
|
2806
|
+
onNodeDragEndRef.current = onNodeDragEnd;
|
|
2807
|
+
onLayoutSettledRef.current = onLayoutSettled;
|
|
2808
|
+
onLayoutTickRef.current = onLayoutTick;
|
|
2566
2809
|
styleRef.current = resolvedStyle;
|
|
2567
2810
|
themeRef.current = resolvedTheme;
|
|
2568
2811
|
selectedNodeIdRef.current = selectedNodeId;
|
|
@@ -2628,7 +2871,8 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2628
2871
|
styleRef.current.labelScale,
|
|
2629
2872
|
styleRef.current.labelThreshold,
|
|
2630
2873
|
styleRef.current.maxLabels,
|
|
2631
|
-
labelFormatterRef.current
|
|
2874
|
+
labelFormatterRef.current,
|
|
2875
|
+
highlightSetRef.current
|
|
2632
2876
|
);
|
|
2633
2877
|
});
|
|
2634
2878
|
const resizeObserver = setupResize(container, state);
|
|
@@ -2653,9 +2897,52 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2653
2897
|
onNodeHover: (node) => {
|
|
2654
2898
|
var _a;
|
|
2655
2899
|
return (_a = onNodeHoverRef.current) == null ? void 0 : _a.call(onNodeHoverRef, node);
|
|
2900
|
+
},
|
|
2901
|
+
onContextMenu: (node, pos) => {
|
|
2902
|
+
var _a;
|
|
2903
|
+
return (_a = onContextMenuRef.current) == null ? void 0 : _a.call(onContextMenuRef, node, pos);
|
|
2904
|
+
},
|
|
2905
|
+
onLinkClick: (link) => {
|
|
2906
|
+
var _a;
|
|
2907
|
+
return (_a = onLinkClickRef.current) == null ? void 0 : _a.call(onLinkClickRef, link);
|
|
2908
|
+
},
|
|
2909
|
+
onLinkHover: (link) => {
|
|
2910
|
+
var _a;
|
|
2911
|
+
return (_a = onLinkHoverRef.current) == null ? void 0 : _a.call(onLinkHoverRef, link);
|
|
2912
|
+
},
|
|
2913
|
+
onNodeDrag: (node, pos) => {
|
|
2914
|
+
var _a;
|
|
2915
|
+
(_a = onNodeDragRef.current) == null ? void 0 : _a.call(onNodeDragRef, node, pos);
|
|
2916
|
+
const d = dataRef.current;
|
|
2917
|
+
const idx = d.nodeIdToIndex.get(node.id);
|
|
2918
|
+
if (idx !== void 0 && d.positions) {
|
|
2919
|
+
d.positions[idx * 3] = pos.x;
|
|
2920
|
+
d.positions[idx * 3 + 1] = pos.y;
|
|
2921
|
+
d.positions[idx * 3 + 2] = pos.z;
|
|
2922
|
+
node.x = pos.x;
|
|
2923
|
+
node.y = pos.y;
|
|
2924
|
+
node.z = pos.z;
|
|
2925
|
+
const gObj = graphObjRef.current;
|
|
2926
|
+
if (gObj && d.scales) {
|
|
2927
|
+
updateNodePositions(gObj.nodesMesh, d.positions, d.scales, d.nodes.length);
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
},
|
|
2931
|
+
onNodeDragEnd: (node, pos) => {
|
|
2932
|
+
var _a;
|
|
2933
|
+
return (_a = onNodeDragEndRef.current) == null ? void 0 : _a.call(onNodeDragEndRef, node, pos);
|
|
2656
2934
|
}
|
|
2657
2935
|
},
|
|
2658
|
-
container
|
|
2936
|
+
container,
|
|
2937
|
+
() => {
|
|
2938
|
+
const d = dataRef.current;
|
|
2939
|
+
return {
|
|
2940
|
+
links: d.links,
|
|
2941
|
+
edgeNodeIndices: d.edgeNodeIndices,
|
|
2942
|
+
edgeLinkIndices: d.edgeLinkIndices,
|
|
2943
|
+
positions: d.positions
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2659
2946
|
);
|
|
2660
2947
|
return () => {
|
|
2661
2948
|
cancelAnim();
|
|
@@ -2795,6 +3082,7 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2795
3082
|
console.error("Graphier: Layout worker error:", err);
|
|
2796
3083
|
};
|
|
2797
3084
|
worker.onmessage = (e) => {
|
|
3085
|
+
var _a, _b;
|
|
2798
3086
|
const msg = e.data;
|
|
2799
3087
|
if (msg.type === "positions") {
|
|
2800
3088
|
const positions = new Float32Array(msg.positions);
|
|
@@ -2822,9 +3110,11 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2822
3110
|
);
|
|
2823
3111
|
}
|
|
2824
3112
|
}
|
|
3113
|
+
(_a = onLayoutTickRef.current) == null ? void 0 : _a.call(onLayoutTickRef, msg.alpha);
|
|
2825
3114
|
}
|
|
2826
3115
|
if (msg.type === "settled") {
|
|
2827
3116
|
dataRef.current.settled = true;
|
|
3117
|
+
(_b = onLayoutSettledRef.current) == null ? void 0 : _b.call(onLayoutSettledRef);
|
|
2828
3118
|
}
|
|
2829
3119
|
};
|
|
2830
3120
|
const layoutParams = resolveLayoutParams(nc, layoutProp);
|
|
@@ -2869,6 +3159,7 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2869
3159
|
const d = dataRef.current;
|
|
2870
3160
|
const theme = themeRef.current;
|
|
2871
3161
|
const style = styleRef.current;
|
|
3162
|
+
highlightSetRef.current = highlightSet;
|
|
2872
3163
|
applyNodeHighlight(
|
|
2873
3164
|
gObj.nodesMesh,
|
|
2874
3165
|
d.nodes,
|
|
@@ -2886,6 +3177,18 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2886
3177
|
d.nodes.length,
|
|
2887
3178
|
theme
|
|
2888
3179
|
);
|
|
3180
|
+
if (sceneState.bloomPass) {
|
|
3181
|
+
const style2 = styleRef.current;
|
|
3182
|
+
if (highlightSet) {
|
|
3183
|
+
sceneState.bloomPass.strength = style2.bloomStrength * 1.8;
|
|
3184
|
+
sceneState.bloomPass.radius = style2.bloomRadius * 1.4;
|
|
3185
|
+
sceneState.bloomPass.threshold = 0.25;
|
|
3186
|
+
} else {
|
|
3187
|
+
sceneState.bloomPass.strength = style2.bloomStrength;
|
|
3188
|
+
sceneState.bloomPass.radius = style2.bloomRadius;
|
|
3189
|
+
sceneState.bloomPass.threshold = style2.bloomThreshold;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
2889
3192
|
const selectedNode = selectedNodeId ? d.nodes.find((n) => n.id === selectedNodeId) : null;
|
|
2890
3193
|
updateSelectionRing(
|
|
2891
3194
|
sceneState.selectionRing,
|
|
@@ -3033,13 +3336,11 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
3033
3336
|
var _a;
|
|
3034
3337
|
return ((_a = sceneRef.current) == null ? void 0 : _a.camera) ?? null;
|
|
3035
3338
|
},
|
|
3036
|
-
|
|
3339
|
+
captureScreenshot() {
|
|
3037
3340
|
const s = sceneRef.current;
|
|
3038
3341
|
if (!s) return null;
|
|
3039
3342
|
s.renderer.render(s.scene, s.camera);
|
|
3040
|
-
return
|
|
3041
|
-
s.renderer.domElement.toBlob(resolve, "image/png");
|
|
3042
|
-
});
|
|
3343
|
+
return s.renderer.domElement.toDataURL("image/png");
|
|
3043
3344
|
}
|
|
3044
3345
|
}));
|
|
3045
3346
|
return /* @__PURE__ */ jsx(
|