@omiron33/omi-neuron-web 0.2.21 → 0.2.23
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 +230 -3
- package/dist/{NeuronWebExplorer-lBM0Jcf9.d.ts → NeuronWebExplorer-OYEmP22R.d.cts} +19 -3
- package/dist/{NeuronWebExplorer-7sRtXdqa.d.cts → NeuronWebExplorer-XMtk_7z-.d.ts} +19 -3
- package/dist/api/index.d.cts +3 -3
- package/dist/api/index.d.ts +3 -3
- package/dist/{chunk-5SZ37JXQ.cjs → chunk-A5KXAYKG.cjs} +519 -9
- package/dist/chunk-A5KXAYKG.cjs.map +1 -0
- package/dist/{chunk-XSDOIONK.js → chunk-QSSXMXPT.js} +518 -10
- package/dist/chunk-QSSXMXPT.js.map +1 -0
- package/dist/{edge-U2Qgwg-K.d.cts → cluster-CU_pBUcK.d.cts} +123 -1
- package/dist/{edge-U2Qgwg-K.d.ts → cluster-CU_pBUcK.d.ts} +123 -1
- package/dist/index.cjs +655 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -7
- package/dist/index.d.ts +66 -7
- package/dist/index.js +641 -4
- package/dist/index.js.map +1 -1
- package/dist/{query-helpers-DMnkjfO0.d.cts → query-helpers-CA23s1ct.d.cts} +2 -102
- package/dist/{query-helpers-BpVwXZJk.d.ts → query-helpers-CdDGFiK3.d.ts} +2 -102
- package/dist/visualization/index.cjs +22 -14
- package/dist/visualization/index.d.cts +19 -6
- package/dist/visualization/index.d.ts +19 -6
- package/dist/visualization/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5SZ37JXQ.cjs.map +0 -1
- package/dist/chunk-XSDOIONK.js.map +0 -1
|
@@ -32,11 +32,26 @@ function _interopNamespace(e) {
|
|
|
32
32
|
var THREE3__namespace = /*#__PURE__*/_interopNamespace(THREE3);
|
|
33
33
|
|
|
34
34
|
// src/visualization/constants.ts
|
|
35
|
+
var DEFAULT_STATUS_COLORS = {
|
|
36
|
+
default: "#c0c5ff",
|
|
37
|
+
// Same as defaultDomainColor (lavender)
|
|
38
|
+
draft: "#9ca3af",
|
|
39
|
+
// Gray
|
|
40
|
+
active: "#4ade80",
|
|
41
|
+
// Green
|
|
42
|
+
complete: "#60a5fa",
|
|
43
|
+
// Blue
|
|
44
|
+
blocked: "#f87171",
|
|
45
|
+
// Red
|
|
46
|
+
archived: "#6b7280"
|
|
47
|
+
// Dark gray
|
|
48
|
+
};
|
|
35
49
|
var DEFAULT_THEME = {
|
|
36
50
|
colors: {
|
|
37
51
|
background: "#020314",
|
|
38
52
|
domainColors: {},
|
|
39
53
|
defaultDomainColor: "#c0c5ff",
|
|
54
|
+
statusColors: DEFAULT_STATUS_COLORS,
|
|
40
55
|
edgeDefault: "#4d4d55",
|
|
41
56
|
edgeActive: "#c6d4ff",
|
|
42
57
|
edgeSelected: "#ffffff",
|
|
@@ -505,9 +520,14 @@ var NodeRenderer = class {
|
|
|
505
520
|
const shouldRenderLabels = labelVisibility !== "none" && (labelVisibility === "interaction" || this.config.maxVisibleLabels > 0 && this.config.labelDistance > 0);
|
|
506
521
|
const resolveNode = (node) => {
|
|
507
522
|
const tier = node.tier ?? "tertiary";
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
523
|
+
let baseColor;
|
|
524
|
+
if (node.status && this.config.statusColors?.[node.status]) {
|
|
525
|
+
baseColor = new THREE3__namespace.Color(this.config.statusColors[node.status]);
|
|
526
|
+
} else {
|
|
527
|
+
baseColor = new THREE3__namespace.Color(
|
|
528
|
+
this.config.domainColors[node.domain] ?? this.config.defaultColor
|
|
529
|
+
);
|
|
530
|
+
}
|
|
511
531
|
const position = new THREE3__namespace.Vector3();
|
|
512
532
|
if (node.position) {
|
|
513
533
|
position.set(...node.position);
|
|
@@ -1952,6 +1972,458 @@ var EdgeRenderer = class {
|
|
|
1952
1972
|
arrow.quaternion.setFromUnitVectors(new THREE3__namespace.Vector3(0, 1, 0), direction);
|
|
1953
1973
|
}
|
|
1954
1974
|
};
|
|
1975
|
+
function computeConvexHull2D(points) {
|
|
1976
|
+
if (points.length < 3) {
|
|
1977
|
+
return points.map((p) => new THREE3__namespace.Vector2(p.x, p.y));
|
|
1978
|
+
}
|
|
1979
|
+
const points2D = points.map((p) => new THREE3__namespace.Vector2(p.x, p.y));
|
|
1980
|
+
let minIdx = 0;
|
|
1981
|
+
for (let i = 1; i < points2D.length; i++) {
|
|
1982
|
+
if (points2D[i].y < points2D[minIdx].y || points2D[i].y === points2D[minIdx].y && points2D[i].x < points2D[minIdx].x) {
|
|
1983
|
+
minIdx = i;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
[points2D[0], points2D[minIdx]] = [points2D[minIdx], points2D[0]];
|
|
1987
|
+
const pivot = points2D[0];
|
|
1988
|
+
const rest = points2D.slice(1).sort((a, b) => {
|
|
1989
|
+
const angleA = Math.atan2(a.y - pivot.y, a.x - pivot.x);
|
|
1990
|
+
const angleB = Math.atan2(b.y - pivot.y, b.x - pivot.x);
|
|
1991
|
+
if (angleA !== angleB) return angleA - angleB;
|
|
1992
|
+
return a.distanceTo(pivot) - b.distanceTo(pivot);
|
|
1993
|
+
});
|
|
1994
|
+
const hull = [pivot];
|
|
1995
|
+
for (const point of rest) {
|
|
1996
|
+
while (hull.length > 1) {
|
|
1997
|
+
const top = hull[hull.length - 1];
|
|
1998
|
+
const nextToTop = hull[hull.length - 2];
|
|
1999
|
+
const cross = (top.x - nextToTop.x) * (point.y - nextToTop.y) - (top.y - nextToTop.y) * (point.x - nextToTop.x);
|
|
2000
|
+
if (cross <= 0) {
|
|
2001
|
+
hull.pop();
|
|
2002
|
+
} else {
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
hull.push(point);
|
|
2007
|
+
}
|
|
2008
|
+
return hull;
|
|
2009
|
+
}
|
|
2010
|
+
function expandHull(hull, padding) {
|
|
2011
|
+
if (hull.length < 3 || padding <= 0) return hull;
|
|
2012
|
+
const centroid = new THREE3__namespace.Vector2(0, 0);
|
|
2013
|
+
for (const p of hull) {
|
|
2014
|
+
centroid.add(p);
|
|
2015
|
+
}
|
|
2016
|
+
centroid.divideScalar(hull.length);
|
|
2017
|
+
return hull.map((p) => {
|
|
2018
|
+
const dir = new THREE3__namespace.Vector2().subVectors(p, centroid).normalize();
|
|
2019
|
+
return new THREE3__namespace.Vector2(p.x + dir.x * padding, p.y + dir.y * padding);
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
var ClusterRenderer = class {
|
|
2023
|
+
constructor(scene, config = {}) {
|
|
2024
|
+
this.scene = scene;
|
|
2025
|
+
this.config = {
|
|
2026
|
+
defaultColor: config.defaultColor ?? "#4a5568",
|
|
2027
|
+
fillOpacity: config.fillOpacity ?? 0.08,
|
|
2028
|
+
strokeOpacity: config.strokeOpacity ?? 0.25,
|
|
2029
|
+
strokeWidth: config.strokeWidth ?? 1.5,
|
|
2030
|
+
labelFontFamily: config.labelFontFamily ?? "system-ui, sans-serif",
|
|
2031
|
+
labelFontSize: config.labelFontSize ?? 11,
|
|
2032
|
+
labelTextColor: config.labelTextColor ?? "#ffffff",
|
|
2033
|
+
labelBackground: config.labelBackground ?? "rgba(0, 0, 0, 0.6)",
|
|
2034
|
+
transitionsEnabled: config.transitionsEnabled ?? true,
|
|
2035
|
+
transitionDurationMs: config.transitionDurationMs ?? 300,
|
|
2036
|
+
zOffset: config.zOffset ?? -0.5,
|
|
2037
|
+
hullPadding: config.hullPadding ?? 1.2
|
|
2038
|
+
};
|
|
2039
|
+
this.scene.add(this.group);
|
|
2040
|
+
}
|
|
2041
|
+
group = new THREE3__namespace.Group();
|
|
2042
|
+
clusterStates = /* @__PURE__ */ new Map();
|
|
2043
|
+
config;
|
|
2044
|
+
/**
|
|
2045
|
+
* Render clusters with convex hull boundaries
|
|
2046
|
+
*/
|
|
2047
|
+
renderClusters(clusters, nodePositions) {
|
|
2048
|
+
const currentIds = new Set(clusters.map((c) => c.id));
|
|
2049
|
+
for (const [id, state] of this.clusterStates) {
|
|
2050
|
+
if (!currentIds.has(id)) {
|
|
2051
|
+
this.removeClusterState(state);
|
|
2052
|
+
this.clusterStates.delete(id);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
for (const cluster of clusters) {
|
|
2056
|
+
const memberPositions = this.getMemberPositions(cluster.nodeIds, nodePositions);
|
|
2057
|
+
if (memberPositions.length < 1) {
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
let state = this.clusterStates.get(cluster.id);
|
|
2061
|
+
if (!state) {
|
|
2062
|
+
state = this.createClusterState(cluster);
|
|
2063
|
+
this.clusterStates.set(cluster.id, state);
|
|
2064
|
+
}
|
|
2065
|
+
this.updateClusterGeometry(state, cluster, memberPositions);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Update cluster positions when nodes move (e.g., ambient motion)
|
|
2070
|
+
*/
|
|
2071
|
+
updatePositions(nodePositions) {
|
|
2072
|
+
for (const [clusterId, state] of this.clusterStates) {
|
|
2073
|
+
let hasChanged = false;
|
|
2074
|
+
for (const [nodeId, lastPos] of state.lastNodePositions) {
|
|
2075
|
+
const currentPos = nodePositions.get(nodeId);
|
|
2076
|
+
if (currentPos && !currentPos.equals(lastPos)) {
|
|
2077
|
+
hasChanged = true;
|
|
2078
|
+
break;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
if (hasChanged) {
|
|
2082
|
+
const nodeIds = Array.from(state.lastNodePositions.keys());
|
|
2083
|
+
const memberPositions = this.getMemberPositions(nodeIds, nodePositions);
|
|
2084
|
+
if (memberPositions.length > 0) {
|
|
2085
|
+
this.updateClusterGeometry(state, { id: clusterId, label: "", nodeIds }, memberPositions);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Update method called each frame
|
|
2092
|
+
*/
|
|
2093
|
+
update(_delta, _elapsed) {
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Clear all clusters
|
|
2097
|
+
*/
|
|
2098
|
+
clear() {
|
|
2099
|
+
for (const state of this.clusterStates.values()) {
|
|
2100
|
+
this.removeClusterState(state);
|
|
2101
|
+
}
|
|
2102
|
+
this.clusterStates.clear();
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Dispose of all resources
|
|
2106
|
+
*/
|
|
2107
|
+
dispose() {
|
|
2108
|
+
this.clear();
|
|
2109
|
+
this.scene.remove(this.group);
|
|
2110
|
+
}
|
|
2111
|
+
getMemberPositions(nodeIds, nodePositions) {
|
|
2112
|
+
const positions = [];
|
|
2113
|
+
for (const nodeId of nodeIds) {
|
|
2114
|
+
const pos = nodePositions.get(nodeId);
|
|
2115
|
+
if (pos) {
|
|
2116
|
+
positions.push(pos.clone());
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
return positions;
|
|
2120
|
+
}
|
|
2121
|
+
createClusterState(cluster) {
|
|
2122
|
+
const color = new THREE3__namespace.Color(cluster.color ?? this.config.defaultColor);
|
|
2123
|
+
const labelElement = document.createElement("div");
|
|
2124
|
+
labelElement.className = "neuron-cluster-label";
|
|
2125
|
+
labelElement.style.cssText = `
|
|
2126
|
+
font-family: ${this.config.labelFontFamily};
|
|
2127
|
+
font-size: ${this.config.labelFontSize}px;
|
|
2128
|
+
font-weight: 500;
|
|
2129
|
+
color: ${this.config.labelTextColor};
|
|
2130
|
+
background: ${this.config.labelBackground};
|
|
2131
|
+
padding: 4px 10px;
|
|
2132
|
+
border-radius: 12px;
|
|
2133
|
+
white-space: nowrap;
|
|
2134
|
+
pointer-events: none;
|
|
2135
|
+
user-select: none;
|
|
2136
|
+
opacity: 0.85;
|
|
2137
|
+
text-transform: uppercase;
|
|
2138
|
+
letter-spacing: 0.5px;
|
|
2139
|
+
`;
|
|
2140
|
+
labelElement.textContent = cluster.label;
|
|
2141
|
+
const label = new CSS2DRenderer_js.CSS2DObject(labelElement);
|
|
2142
|
+
label.position.set(0, 0, 0);
|
|
2143
|
+
this.group.add(label);
|
|
2144
|
+
return {
|
|
2145
|
+
id: cluster.id,
|
|
2146
|
+
mesh: null,
|
|
2147
|
+
outline: null,
|
|
2148
|
+
label,
|
|
2149
|
+
labelElement,
|
|
2150
|
+
color,
|
|
2151
|
+
centroid: new THREE3__namespace.Vector3(),
|
|
2152
|
+
lastNodePositions: /* @__PURE__ */ new Map()
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
updateClusterGeometry(state, cluster, memberPositions) {
|
|
2156
|
+
if (cluster.color) {
|
|
2157
|
+
state.color.set(cluster.color);
|
|
2158
|
+
}
|
|
2159
|
+
if (state.labelElement && cluster.label) {
|
|
2160
|
+
state.labelElement.textContent = cluster.label;
|
|
2161
|
+
}
|
|
2162
|
+
state.lastNodePositions.clear();
|
|
2163
|
+
for (let i = 0; i < cluster.nodeIds.length; i++) {
|
|
2164
|
+
if (memberPositions[i]) {
|
|
2165
|
+
state.lastNodePositions.set(cluster.nodeIds[i], memberPositions[i].clone());
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
state.centroid.set(0, 0, 0);
|
|
2169
|
+
for (const pos of memberPositions) {
|
|
2170
|
+
state.centroid.add(pos);
|
|
2171
|
+
}
|
|
2172
|
+
state.centroid.divideScalar(memberPositions.length);
|
|
2173
|
+
if (cluster.position) {
|
|
2174
|
+
state.centroid.set(cluster.position.x, cluster.position.y, cluster.position.z);
|
|
2175
|
+
}
|
|
2176
|
+
if (state.label) {
|
|
2177
|
+
state.label.position.copy(state.centroid);
|
|
2178
|
+
state.label.position.y += 1.5;
|
|
2179
|
+
}
|
|
2180
|
+
if (memberPositions.length < 3) {
|
|
2181
|
+
this.removeClusterMesh(state);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const hull2D = computeConvexHull2D(memberPositions);
|
|
2185
|
+
const expandedHull = expandHull(hull2D, this.config.hullPadding ?? 1.2);
|
|
2186
|
+
if (expandedHull.length < 3) {
|
|
2187
|
+
this.removeClusterMesh(state);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
const shape = new THREE3__namespace.Shape(expandedHull);
|
|
2191
|
+
const geometry = new THREE3__namespace.ShapeGeometry(shape);
|
|
2192
|
+
const avgZ = memberPositions.reduce((sum, p) => sum + p.z, 0) / memberPositions.length + (this.config.zOffset ?? -0.5);
|
|
2193
|
+
if (state.mesh) {
|
|
2194
|
+
state.mesh.geometry.dispose();
|
|
2195
|
+
state.mesh.geometry = geometry;
|
|
2196
|
+
state.mesh.position.z = avgZ;
|
|
2197
|
+
state.mesh.material.color.copy(state.color);
|
|
2198
|
+
} else {
|
|
2199
|
+
const material = new THREE3__namespace.MeshBasicMaterial({
|
|
2200
|
+
color: state.color,
|
|
2201
|
+
transparent: true,
|
|
2202
|
+
opacity: this.config.fillOpacity,
|
|
2203
|
+
side: THREE3__namespace.DoubleSide,
|
|
2204
|
+
depthWrite: false
|
|
2205
|
+
});
|
|
2206
|
+
state.mesh = new THREE3__namespace.Mesh(geometry, material);
|
|
2207
|
+
state.mesh.position.z = avgZ;
|
|
2208
|
+
state.mesh.renderOrder = -1;
|
|
2209
|
+
this.group.add(state.mesh);
|
|
2210
|
+
}
|
|
2211
|
+
const outlinePoints = [...expandedHull, expandedHull[0]].map(
|
|
2212
|
+
(p) => new THREE3__namespace.Vector3(p.x, p.y, avgZ + 0.01)
|
|
2213
|
+
);
|
|
2214
|
+
if (state.outline) {
|
|
2215
|
+
state.outline.geometry.dispose();
|
|
2216
|
+
state.outline.geometry = new THREE3__namespace.BufferGeometry().setFromPoints(outlinePoints);
|
|
2217
|
+
state.outline.material.color.copy(state.color);
|
|
2218
|
+
} else {
|
|
2219
|
+
const outlineGeometry = new THREE3__namespace.BufferGeometry().setFromPoints(outlinePoints);
|
|
2220
|
+
const outlineMaterial = new THREE3__namespace.LineBasicMaterial({
|
|
2221
|
+
color: state.color,
|
|
2222
|
+
transparent: true,
|
|
2223
|
+
opacity: this.config.strokeOpacity,
|
|
2224
|
+
linewidth: this.config.strokeWidth
|
|
2225
|
+
});
|
|
2226
|
+
state.outline = new THREE3__namespace.Line(outlineGeometry, outlineMaterial);
|
|
2227
|
+
state.outline.renderOrder = -1;
|
|
2228
|
+
this.group.add(state.outline);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
removeClusterMesh(state) {
|
|
2232
|
+
if (state.mesh) {
|
|
2233
|
+
state.mesh.geometry.dispose();
|
|
2234
|
+
state.mesh.material.dispose();
|
|
2235
|
+
this.group.remove(state.mesh);
|
|
2236
|
+
state.mesh = null;
|
|
2237
|
+
}
|
|
2238
|
+
if (state.outline) {
|
|
2239
|
+
state.outline.geometry.dispose();
|
|
2240
|
+
state.outline.material.dispose();
|
|
2241
|
+
this.group.remove(state.outline);
|
|
2242
|
+
state.outline = null;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
removeClusterState(state) {
|
|
2246
|
+
this.removeClusterMesh(state);
|
|
2247
|
+
if (state.label) {
|
|
2248
|
+
if (state.labelElement?.parentNode) {
|
|
2249
|
+
state.labelElement.parentNode.removeChild(state.labelElement);
|
|
2250
|
+
}
|
|
2251
|
+
this.group.remove(state.label);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Get cluster visibility state for external use
|
|
2256
|
+
*/
|
|
2257
|
+
getClusterIds() {
|
|
2258
|
+
return Array.from(this.clusterStates.keys());
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Get centroid position for a cluster
|
|
2262
|
+
*/
|
|
2263
|
+
getClusterCentroid(clusterId) {
|
|
2264
|
+
const state = this.clusterStates.get(clusterId);
|
|
2265
|
+
return state ? state.centroid.clone() : null;
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
|
|
2269
|
+
// src/visualization/layouts/tree-layout.ts
|
|
2270
|
+
function applyTreeLayout(nodes, edges, options = {}) {
|
|
2271
|
+
if (nodes.length === 0) return nodes;
|
|
2272
|
+
const treeOptions = options.tree ?? {};
|
|
2273
|
+
const horizontalSpacing = treeOptions.horizontalSpacing ?? 3;
|
|
2274
|
+
const verticalSpacing = treeOptions.verticalSpacing ?? 4;
|
|
2275
|
+
const direction = treeOptions.direction ?? "down";
|
|
2276
|
+
const rootNodeId = treeOptions.rootNodeId;
|
|
2277
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
2278
|
+
const nodeBySlug = /* @__PURE__ */ new Map();
|
|
2279
|
+
for (const node of nodes) {
|
|
2280
|
+
nodeById.set(node.id, node);
|
|
2281
|
+
if (node.slug) {
|
|
2282
|
+
nodeBySlug.set(node.slug, node);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
2286
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
2287
|
+
for (const edge of edges) {
|
|
2288
|
+
const fromNode = nodeBySlug.get(edge.from) ?? nodeById.get(edge.from);
|
|
2289
|
+
const toNode = nodeBySlug.get(edge.to) ?? nodeById.get(edge.to);
|
|
2290
|
+
if (!fromNode || !toNode) continue;
|
|
2291
|
+
const parentId = fromNode.id;
|
|
2292
|
+
const childId = toNode.id;
|
|
2293
|
+
if (!parentMap.has(childId)) {
|
|
2294
|
+
parentMap.set(childId, parentId);
|
|
2295
|
+
const siblings = childrenMap.get(parentId) ?? [];
|
|
2296
|
+
siblings.push(childId);
|
|
2297
|
+
childrenMap.set(parentId, siblings);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
let roots;
|
|
2301
|
+
if (rootNodeId) {
|
|
2302
|
+
const rootNode = nodeBySlug.get(rootNodeId) ?? nodeById.get(rootNodeId);
|
|
2303
|
+
roots = rootNode ? [rootNode.id] : [];
|
|
2304
|
+
} else {
|
|
2305
|
+
roots = nodes.filter((n) => !parentMap.has(n.id)).map((n) => n.id);
|
|
2306
|
+
}
|
|
2307
|
+
if (roots.length === 0 && nodes.length > 0) {
|
|
2308
|
+
roots = [nodes[0].id];
|
|
2309
|
+
}
|
|
2310
|
+
const treeNodes = /* @__PURE__ */ new Map();
|
|
2311
|
+
function calculateWidth(nodeId, depth) {
|
|
2312
|
+
const children = childrenMap.get(nodeId) ?? [];
|
|
2313
|
+
let width;
|
|
2314
|
+
if (children.length === 0) {
|
|
2315
|
+
width = 1;
|
|
2316
|
+
} else {
|
|
2317
|
+
width = 0;
|
|
2318
|
+
for (const childId of children) {
|
|
2319
|
+
width += calculateWidth(childId, depth + 1);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
treeNodes.set(nodeId, {
|
|
2323
|
+
id: nodeId,
|
|
2324
|
+
children,
|
|
2325
|
+
width,
|
|
2326
|
+
depth,
|
|
2327
|
+
x: 0
|
|
2328
|
+
});
|
|
2329
|
+
return width;
|
|
2330
|
+
}
|
|
2331
|
+
let totalWidth = 0;
|
|
2332
|
+
for (const rootId of roots) {
|
|
2333
|
+
totalWidth += calculateWidth(rootId, 0);
|
|
2334
|
+
}
|
|
2335
|
+
function assignPositions(nodeId, leftBound) {
|
|
2336
|
+
const treeNode = treeNodes.get(nodeId);
|
|
2337
|
+
if (!treeNode) return;
|
|
2338
|
+
const children = treeNode.children;
|
|
2339
|
+
if (children.length === 0) {
|
|
2340
|
+
treeNode.x = leftBound + 0.5;
|
|
2341
|
+
} else {
|
|
2342
|
+
let childLeft = leftBound;
|
|
2343
|
+
for (const childId of children) {
|
|
2344
|
+
assignPositions(childId, childLeft);
|
|
2345
|
+
const childNode = treeNodes.get(childId);
|
|
2346
|
+
if (childNode) {
|
|
2347
|
+
childLeft += childNode.width;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
const firstChild = treeNodes.get(children[0]);
|
|
2351
|
+
const lastChild = treeNodes.get(children[children.length - 1]);
|
|
2352
|
+
if (firstChild && lastChild) {
|
|
2353
|
+
treeNode.x = (firstChild.x + lastChild.x) / 2;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
let currentLeft = 0;
|
|
2358
|
+
for (const rootId of roots) {
|
|
2359
|
+
assignPositions(rootId, currentLeft);
|
|
2360
|
+
const rootTree = treeNodes.get(rootId);
|
|
2361
|
+
if (rootTree) {
|
|
2362
|
+
currentLeft += rootTree.width;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
const positions = /* @__PURE__ */ new Map();
|
|
2366
|
+
const centerOffset = totalWidth / 2;
|
|
2367
|
+
const isHorizontal = direction === "left" || direction === "right";
|
|
2368
|
+
for (const [nodeId, treeNode] of treeNodes) {
|
|
2369
|
+
const siblingPos = (treeNode.x - centerOffset) * horizontalSpacing;
|
|
2370
|
+
let depthPos;
|
|
2371
|
+
switch (direction) {
|
|
2372
|
+
case "up":
|
|
2373
|
+
depthPos = treeNode.depth * verticalSpacing;
|
|
2374
|
+
break;
|
|
2375
|
+
case "left":
|
|
2376
|
+
depthPos = -treeNode.depth * verticalSpacing;
|
|
2377
|
+
break;
|
|
2378
|
+
case "right":
|
|
2379
|
+
depthPos = treeNode.depth * verticalSpacing;
|
|
2380
|
+
break;
|
|
2381
|
+
case "down":
|
|
2382
|
+
default:
|
|
2383
|
+
depthPos = -treeNode.depth * verticalSpacing;
|
|
2384
|
+
break;
|
|
2385
|
+
}
|
|
2386
|
+
const x = isHorizontal ? depthPos : siblingPos;
|
|
2387
|
+
const y = isHorizontal ? siblingPos : depthPos;
|
|
2388
|
+
positions.set(nodeId, [x, y, 0]);
|
|
2389
|
+
}
|
|
2390
|
+
const positionedIds = new Set(positions.keys());
|
|
2391
|
+
const orphans = nodes.filter((n) => !positionedIds.has(n.id));
|
|
2392
|
+
if (orphans.length > 0) {
|
|
2393
|
+
const maxDepth = Math.max(...Array.from(treeNodes.values()).map((t) => t.depth), 0);
|
|
2394
|
+
const orphanDepth = (maxDepth + 2) * verticalSpacing;
|
|
2395
|
+
const orphanSpread = (orphans.length - 1) * horizontalSpacing / 2;
|
|
2396
|
+
orphans.forEach((orphan, index) => {
|
|
2397
|
+
const siblingPos = -orphanSpread + index * horizontalSpacing;
|
|
2398
|
+
let depthPos;
|
|
2399
|
+
switch (direction) {
|
|
2400
|
+
case "up":
|
|
2401
|
+
depthPos = orphanDepth;
|
|
2402
|
+
break;
|
|
2403
|
+
case "left":
|
|
2404
|
+
depthPos = -orphanDepth;
|
|
2405
|
+
break;
|
|
2406
|
+
case "right":
|
|
2407
|
+
depthPos = orphanDepth;
|
|
2408
|
+
break;
|
|
2409
|
+
case "down":
|
|
2410
|
+
default:
|
|
2411
|
+
depthPos = -orphanDepth;
|
|
2412
|
+
break;
|
|
2413
|
+
}
|
|
2414
|
+
const x = isHorizontal ? depthPos : siblingPos;
|
|
2415
|
+
const y = isHorizontal ? siblingPos : depthPos;
|
|
2416
|
+
positions.set(orphan.id, [x, y, 0]);
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
return nodes.map((node) => {
|
|
2420
|
+
const position = positions.get(node.id);
|
|
2421
|
+
if (position) {
|
|
2422
|
+
return { ...node, position };
|
|
2423
|
+
}
|
|
2424
|
+
return node;
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
1955
2427
|
|
|
1956
2428
|
// src/visualization/layouts/fuzzy-layout.ts
|
|
1957
2429
|
var GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
|
|
@@ -2004,11 +2476,14 @@ function mulberry32(seed) {
|
|
|
2004
2476
|
function buildSeed(baseSeed, nodeKey) {
|
|
2005
2477
|
return mulberry32(hashString(`${baseSeed}:${nodeKey}`));
|
|
2006
2478
|
}
|
|
2007
|
-
function applyFuzzyLayout(nodes, options = {}) {
|
|
2479
|
+
function applyFuzzyLayout(nodes, options = {}, edges) {
|
|
2008
2480
|
const mode = options.mode ?? "atlas";
|
|
2009
2481
|
if (mode === "positioned") {
|
|
2010
2482
|
return nodes;
|
|
2011
2483
|
}
|
|
2484
|
+
if (mode === "tree") {
|
|
2485
|
+
return applyTreeLayout(nodes, edges ?? [], options);
|
|
2486
|
+
}
|
|
2012
2487
|
const needsLayout = nodes.some((node) => !node.position);
|
|
2013
2488
|
if (mode === "auto" && !needsLayout) {
|
|
2014
2489
|
return nodes;
|
|
@@ -2674,6 +3149,7 @@ function NeuronWeb({
|
|
|
2674
3149
|
const transitionsEnabled = resolvedPerformanceMode === "normal" && !prefersReducedMotion && profileAllowsContinuous && resolvedAnimationConfig.transitionDurationMs > 0;
|
|
2675
3150
|
return new NodeRenderer(sceneManager.scene, {
|
|
2676
3151
|
domainColors: resolvedTheme.colors.domainColors,
|
|
3152
|
+
statusColors: resolvedTheme.colors.statusColors,
|
|
2677
3153
|
defaultColor: resolvedTheme.colors.defaultDomainColor,
|
|
2678
3154
|
baseScale: 1.15,
|
|
2679
3155
|
tierScales: {
|
|
@@ -2778,6 +3254,27 @@ function NeuronWeb({
|
|
|
2778
3254
|
edgeRenderer?.dispose();
|
|
2779
3255
|
};
|
|
2780
3256
|
}, [edgeRenderer]);
|
|
3257
|
+
const clusterRenderer = react.useMemo(() => {
|
|
3258
|
+
if (!sceneManager) return null;
|
|
3259
|
+
if (!graphData.clusters?.length) return null;
|
|
3260
|
+
return new ClusterRenderer(sceneManager.scene, {
|
|
3261
|
+
defaultColor: resolvedTheme.colors.defaultDomainColor,
|
|
3262
|
+
fillOpacity: 0.08,
|
|
3263
|
+
strokeOpacity: 0.25,
|
|
3264
|
+
strokeWidth: 1.5,
|
|
3265
|
+
labelFontFamily: resolvedTheme.typography.labelFontFamily,
|
|
3266
|
+
labelFontSize: resolvedTheme.typography.labelFontSize - 1,
|
|
3267
|
+
labelTextColor: resolvedTheme.colors.labelText,
|
|
3268
|
+
labelBackground: resolvedTheme.colors.labelBackground,
|
|
3269
|
+
zOffset: -0.5,
|
|
3270
|
+
hullPadding: 1.2
|
|
3271
|
+
});
|
|
3272
|
+
}, [sceneManager, graphData.clusters?.length, resolvedTheme]);
|
|
3273
|
+
react.useEffect(() => {
|
|
3274
|
+
return () => {
|
|
3275
|
+
clusterRenderer?.dispose();
|
|
3276
|
+
};
|
|
3277
|
+
}, [clusterRenderer]);
|
|
2781
3278
|
const doubleClickEnabled = false;
|
|
2782
3279
|
const interactionManager = react.useMemo(() => {
|
|
2783
3280
|
if (!sceneManager) return null;
|
|
@@ -2820,8 +3317,8 @@ function NeuronWeb({
|
|
|
2820
3317
|
[layout, resolvedDensity.spread]
|
|
2821
3318
|
);
|
|
2822
3319
|
const resolvedNodes = react.useMemo(
|
|
2823
|
-
() => applyFuzzyLayout(workingGraph.nodes, layoutOptions),
|
|
2824
|
-
[workingGraph.nodes, layoutOptions]
|
|
3320
|
+
() => applyFuzzyLayout(workingGraph.nodes, layoutOptions, workingGraph.edges),
|
|
3321
|
+
[workingGraph.nodes, workingGraph.edges, layoutOptions]
|
|
2825
3322
|
);
|
|
2826
3323
|
const displayNodes = react.useMemo(() => {
|
|
2827
3324
|
if (!selectedNodeId || !resolvedDensity.focusExpansion) return resolvedNodes;
|
|
@@ -3203,7 +3700,18 @@ function NeuronWeb({
|
|
|
3203
3700
|
nodeRenderer.renderNodes(displayNodes);
|
|
3204
3701
|
const positions = nodeRenderer.getNodePositionsBySlug(/* @__PURE__ */ new Map());
|
|
3205
3702
|
edgeRenderer.renderEdges(workingGraph.edges, positions);
|
|
3206
|
-
|
|
3703
|
+
if (clusterRenderer && graphData.clusters?.length) {
|
|
3704
|
+
const nodePositionsById = /* @__PURE__ */ new Map();
|
|
3705
|
+
for (const node of displayNodes) {
|
|
3706
|
+
const pos = nodeRenderer.getNodePosition(node.id);
|
|
3707
|
+
if (pos) {
|
|
3708
|
+
nodePositionsById.set(node.id, pos);
|
|
3709
|
+
nodePositionsById.set(node.slug, pos);
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
clusterRenderer.renderClusters(graphData.clusters, nodePositionsById);
|
|
3713
|
+
}
|
|
3714
|
+
}, [displayNodes, workingGraph.edges, graphData.clusters, sceneManager, nodeRenderer, edgeRenderer, clusterRenderer]);
|
|
3207
3715
|
react.useEffect(() => {
|
|
3208
3716
|
if (!sceneManager) return;
|
|
3209
3717
|
sceneManager.updateBackground(resolvedTheme.colors.background);
|
|
@@ -4126,6 +4634,7 @@ function dedupePreserveOrder(values) {
|
|
|
4126
4634
|
}
|
|
4127
4635
|
|
|
4128
4636
|
exports.DEFAULT_RENDERING_OPTIONS = DEFAULT_RENDERING_OPTIONS;
|
|
4637
|
+
exports.DEFAULT_STATUS_COLORS = DEFAULT_STATUS_COLORS;
|
|
4129
4638
|
exports.DEFAULT_THEME = DEFAULT_THEME;
|
|
4130
4639
|
exports.NeuronContext = NeuronContext;
|
|
4131
4640
|
exports.NeuronWeb = NeuronWeb;
|
|
@@ -4133,6 +4642,7 @@ exports.NeuronWebExplorer = NeuronWebExplorer;
|
|
|
4133
4642
|
exports.SceneManager = SceneManager;
|
|
4134
4643
|
exports.ThemeEngine = ThemeEngine;
|
|
4135
4644
|
exports.applyFuzzyLayout = applyFuzzyLayout;
|
|
4645
|
+
exports.applyTreeLayout = applyTreeLayout;
|
|
4136
4646
|
exports.createStoryBeat = createStoryBeat;
|
|
4137
4647
|
exports.createStudyPathFromBeat = createStudyPathFromBeat;
|
|
4138
4648
|
exports.createStudyPathFromNodeIds = createStudyPathFromNodeIds;
|
|
@@ -4141,5 +4651,5 @@ exports.normalizeStoryBeat = normalizeStoryBeat;
|
|
|
4141
4651
|
exports.useNeuronContext = useNeuronContext;
|
|
4142
4652
|
exports.useNeuronGraph = useNeuronGraph;
|
|
4143
4653
|
exports.validateStoryBeat = validateStoryBeat;
|
|
4144
|
-
//# sourceMappingURL=chunk-
|
|
4145
|
-
//# sourceMappingURL=chunk-
|
|
4654
|
+
//# sourceMappingURL=chunk-A5KXAYKG.cjs.map
|
|
4655
|
+
//# sourceMappingURL=chunk-A5KXAYKG.cjs.map
|