@andespindola/brainlink 0.1.0-beta.104 → 0.1.0-beta.106
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 +1 -1
- package/dist/application/frontend/client-js.js +133 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -604,7 +604,7 @@ The graph UI shows:
|
|
|
604
604
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
605
605
|
- compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
|
|
606
606
|
- graph camera treats hub-centered navigation as structural only when the hub is dominant; diffuse stress graphs reset and zoom around the full graph mass
|
|
607
|
-
- graph LOD progression:
|
|
607
|
+
- graph LOD progression: hierarchical rendering now follows one recursive graph-of-graphs standard whenever a graph has more than one hierarchy level; each level expands through intermediate subgraph sizes (instead of jumping directly to leaves), starts from a memory-hub-centered mesh, and each supernode can expand into another same-shape subgraph level (up to 999 children) with latent fade-in, aggregated real links and local sibling mesh links so org-heavy-like and stress-50k-like structures share the same layered behavior at different depths; layered clusters also receive perspective depth projection (Z-depth) so expansion reads as a true depth field instead of a flat 2D switch; for massive graphs the first expansion starts deeper in zoom and is additionally gated by focus readiness (screen-space isolation of the focused parent) so child levels open only when that subgraph is truly centered and separated in view
|
|
608
608
|
|
|
609
609
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
610
610
|
|
|
@@ -31,6 +31,10 @@ const massiveEcosystemClusterScaleThreshold = 4.2
|
|
|
31
31
|
const ecosystemSubgraphScaleThreshold = 0.18
|
|
32
32
|
const ecosystemMicroScaleThreshold = 0.08
|
|
33
33
|
const ecosystemFocusedParentLimit = 2
|
|
34
|
+
const ecosystemDepthNear = 36
|
|
35
|
+
const ecosystemDepthFar = 560
|
|
36
|
+
const ecosystemDepthPerspective = 1400
|
|
37
|
+
const ecosystemDepthOpacityFloor = 0.42
|
|
34
38
|
const zoomRecoveryGuardMs = 4200
|
|
35
39
|
const zoomCapTargetViewportShare = 0.72
|
|
36
40
|
const meshEdgeScaleThreshold = 0.09
|
|
@@ -596,7 +600,7 @@ const recomputeVisibility = () => {
|
|
|
596
600
|
y: macroHub ? macroHub.y : (bounds.minY + bounds.maxY) / 2
|
|
597
601
|
}
|
|
598
602
|
: { x: 0, y: 0 }
|
|
599
|
-
const ecosystemGraph = nodes.length >
|
|
603
|
+
const ecosystemGraph = nodes.length > 1
|
|
600
604
|
? buildEcosystemGraph(nodes, state.macroCenter, primaryHub)
|
|
601
605
|
: {
|
|
602
606
|
clusters: [],
|
|
@@ -768,12 +772,29 @@ const ecosystemLayoutSpacingForSize = size => {
|
|
|
768
772
|
return 7
|
|
769
773
|
}
|
|
770
774
|
|
|
775
|
+
const buildIntermediateEcosystemSizes = (fromSize, toSize) => {
|
|
776
|
+
if (fromSize <= toSize + 1) {
|
|
777
|
+
return []
|
|
778
|
+
}
|
|
779
|
+
const intermediate = []
|
|
780
|
+
let current = fromSize
|
|
781
|
+
while (current > toSize + 1) {
|
|
782
|
+
const stepped = Math.max(toSize + 1, Math.ceil(current / 3))
|
|
783
|
+
if (stepped >= current) {
|
|
784
|
+
break
|
|
785
|
+
}
|
|
786
|
+
intermediate.push(stepped)
|
|
787
|
+
current = stepped
|
|
788
|
+
}
|
|
789
|
+
return intermediate
|
|
790
|
+
}
|
|
791
|
+
|
|
771
792
|
const buildEcosystemLevelSizes = nodeCount => {
|
|
772
793
|
if (nodeCount <= 0) return []
|
|
773
|
-
const
|
|
794
|
+
const primarySizes = []
|
|
774
795
|
let currentSize = Math.max(1, Math.ceil(nodeCount / ecosystemLevelNodeCap))
|
|
775
796
|
while (currentSize >= 1) {
|
|
776
|
-
|
|
797
|
+
primarySizes.push(currentSize)
|
|
777
798
|
if (currentSize === 1) {
|
|
778
799
|
break
|
|
779
800
|
}
|
|
@@ -783,7 +804,28 @@ const buildEcosystemLevelSizes = nodeCount => {
|
|
|
783
804
|
}
|
|
784
805
|
currentSize = nextSize
|
|
785
806
|
}
|
|
786
|
-
|
|
807
|
+
const expandedSizes = []
|
|
808
|
+
for (let index = 0; index < primarySizes.length; index += 1) {
|
|
809
|
+
const size = primarySizes[index]
|
|
810
|
+
if (expandedSizes.length === 0 || expandedSizes[expandedSizes.length - 1] !== size) {
|
|
811
|
+
expandedSizes.push(size)
|
|
812
|
+
}
|
|
813
|
+
const nextSize = primarySizes[index + 1]
|
|
814
|
+
if (!Number.isFinite(nextSize)) {
|
|
815
|
+
continue
|
|
816
|
+
}
|
|
817
|
+
const intermediate = buildIntermediateEcosystemSizes(size, nextSize)
|
|
818
|
+
for (let intermediateIndex = 0; intermediateIndex < intermediate.length; intermediateIndex += 1) {
|
|
819
|
+
const candidate = intermediate[intermediateIndex]
|
|
820
|
+
if (expandedSizes[expandedSizes.length - 1] !== candidate) {
|
|
821
|
+
expandedSizes.push(candidate)
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (expandedSizes[expandedSizes.length - 1] !== 1) {
|
|
826
|
+
expandedSizes.push(1)
|
|
827
|
+
}
|
|
828
|
+
return expandedSizes
|
|
787
829
|
}
|
|
788
830
|
|
|
789
831
|
const buildEcosystemExpansionLevels = (levelSizes, nodeCount) => {
|
|
@@ -794,12 +836,12 @@ const buildEcosystemExpansionLevels = (levelSizes, nodeCount) => {
|
|
|
794
836
|
const maxScale = isMassive
|
|
795
837
|
? massiveEcosystemClusterScaleThreshold
|
|
796
838
|
: ecosystemClusterScaleThreshold
|
|
797
|
-
const startScale = isMassive ?
|
|
839
|
+
const startScale = isMassive ? 1.12 : 0.24
|
|
798
840
|
const transitionCount = levelSizes.length - 1
|
|
799
841
|
const usableScale = Math.max(0.08, maxScale - startScale)
|
|
800
842
|
const step = usableScale / transitionCount
|
|
801
|
-
const stride = isMassive ? 0.
|
|
802
|
-
const overlap = isMassive ? 1.
|
|
843
|
+
const stride = isMassive ? 0.93 : 0.82
|
|
844
|
+
const overlap = isMassive ? 1.22 : 1.62
|
|
803
845
|
const levels = []
|
|
804
846
|
for (let index = 0; index < transitionCount; index += 1) {
|
|
805
847
|
const start = startScale + step * index * stride
|
|
@@ -1115,6 +1157,81 @@ const selectHierarchicalEcosystemClusters = viewport => {
|
|
|
1115
1157
|
return [...hubClusters, ...visibleClusters]
|
|
1116
1158
|
}
|
|
1117
1159
|
|
|
1160
|
+
const ecosystemLevelIndexBySize = () => {
|
|
1161
|
+
const indexBySize = new Map()
|
|
1162
|
+
for (let index = 0; index < state.ecosystemLevelSizes.length; index += 1) {
|
|
1163
|
+
indexBySize.set(state.ecosystemLevelSizes[index], index)
|
|
1164
|
+
}
|
|
1165
|
+
return indexBySize
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const ecosystemDepthForCluster = (cluster, levelIndexMap) => {
|
|
1169
|
+
if (cluster.isHub) {
|
|
1170
|
+
return ecosystemDepthNear
|
|
1171
|
+
}
|
|
1172
|
+
const maxLevelIndex = Math.max(state.ecosystemLevelSizes.length - 1, 0)
|
|
1173
|
+
const levelIndex = levelIndexMap.get(cluster.size) ?? 0
|
|
1174
|
+
const reverseIndex = Math.max(0, maxLevelIndex - levelIndex)
|
|
1175
|
+
const normalized = maxLevelIndex === 0 ? 0 : reverseIndex / maxLevelIndex
|
|
1176
|
+
return ecosystemDepthNear + normalized * (ecosystemDepthFar - ecosystemDepthNear)
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
const projectEcosystemPoint = (x, y, depth, anchor) => {
|
|
1180
|
+
const factor = ecosystemDepthPerspective / (ecosystemDepthPerspective + Math.max(0, depth))
|
|
1181
|
+
return {
|
|
1182
|
+
x: anchor.x + (x - anchor.x) * factor,
|
|
1183
|
+
y: anchor.y + (y - anchor.y) * factor,
|
|
1184
|
+
factor
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
|
|
1189
|
+
const levelIndexMap = ecosystemLevelIndexBySize()
|
|
1190
|
+
const projectedClusters = []
|
|
1191
|
+
const clusterById = new Map()
|
|
1192
|
+
|
|
1193
|
+
for (let index = 0; index < clusters.length; index += 1) {
|
|
1194
|
+
const cluster = clusters[index]
|
|
1195
|
+
const depth = ecosystemDepthForCluster(cluster, levelIndexMap)
|
|
1196
|
+
const projected = projectEcosystemPoint(cluster.x, cluster.y, depth, anchor)
|
|
1197
|
+
const baseOpacity = Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1
|
|
1198
|
+
const depthOpacity = Math.max(
|
|
1199
|
+
ecosystemDepthOpacityFloor,
|
|
1200
|
+
Math.min(1, 0.58 + projected.factor * 0.62)
|
|
1201
|
+
)
|
|
1202
|
+
const projectedCluster = {
|
|
1203
|
+
...cluster,
|
|
1204
|
+
x: projected.x,
|
|
1205
|
+
y: projected.y,
|
|
1206
|
+
lodOpacity: baseOpacity * depthOpacity,
|
|
1207
|
+
depth,
|
|
1208
|
+
depthScale: projected.factor
|
|
1209
|
+
}
|
|
1210
|
+
projectedClusters.push(projectedCluster)
|
|
1211
|
+
clusterById.set(projectedCluster.id, projectedCluster)
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const projectedEdges = edges
|
|
1215
|
+
.map((edge) => {
|
|
1216
|
+
const sourceCluster = clusterById.get(edge.sourceCluster.id)
|
|
1217
|
+
const targetCluster = clusterById.get(edge.targetCluster.id)
|
|
1218
|
+
if (!sourceCluster || !targetCluster) {
|
|
1219
|
+
return null
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
...edge,
|
|
1223
|
+
sourceCluster,
|
|
1224
|
+
targetCluster
|
|
1225
|
+
}
|
|
1226
|
+
})
|
|
1227
|
+
.filter(Boolean)
|
|
1228
|
+
|
|
1229
|
+
return {
|
|
1230
|
+
clusters: projectedClusters,
|
|
1231
|
+
edges: projectedEdges
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1118
1235
|
const ecosystemSiblingEdgesForClusters = (clusters, existingEdges) => {
|
|
1119
1236
|
const byParent = new Map()
|
|
1120
1237
|
for (let index = 0; index < clusters.length; index += 1) {
|
|
@@ -2813,7 +2930,9 @@ const clusterRadiusPx = cluster => {
|
|
|
2813
2930
|
const size = Math.max(1, Math.min(ecosystemLevelNodeCap, cluster.size || cluster.count || 1))
|
|
2814
2931
|
const sizeBias = 0.56 + Math.log10(size + 1) * 0.28
|
|
2815
2932
|
const densityBias = Math.log10((cluster.count || 1) + 1) * 0.12
|
|
2816
|
-
|
|
2933
|
+
const radius = Math.max(0.62, Math.min(2.4, sizeBias + densityBias))
|
|
2934
|
+
const depthScale = Number.isFinite(cluster.depthScale) ? cluster.depthScale : 1
|
|
2935
|
+
return Math.max(0.56, Math.min(3.2, radius * depthScale))
|
|
2817
2936
|
}
|
|
2818
2937
|
return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
2819
2938
|
}
|
|
@@ -2985,14 +3104,17 @@ const computeRenderVisibility = () => {
|
|
|
2985
3104
|
? massiveEcosystemClusterScaleThreshold
|
|
2986
3105
|
: ecosystemClusterScaleThreshold
|
|
2987
3106
|
if (
|
|
2988
|
-
state.
|
|
3107
|
+
state.ecosystemExpansionLevels.length > 0 &&
|
|
2989
3108
|
state.transform.scale <= ecosystemScaleThreshold &&
|
|
2990
3109
|
state.ecosystemClusters.length > 0
|
|
2991
3110
|
) {
|
|
2992
3111
|
const clusters = selectHierarchicalEcosystemClusters(viewport)
|
|
2993
3112
|
.sort((left, right) => right.count - left.count)
|
|
2994
|
-
|
|
2995
|
-
|
|
3113
|
+
const edges = ecosystemEdgesForClusters(clusters)
|
|
3114
|
+
const projectionAnchor = ecosystemFocusPoint()
|
|
3115
|
+
const projected = applyEcosystemDepthProjection(clusters, edges, projectionAnchor)
|
|
3116
|
+
state.renderClusters = projected.clusters
|
|
3117
|
+
state.renderClusterEdges = projected.edges
|
|
2996
3118
|
state.renderNodes = []
|
|
2997
3119
|
state.renderEdges = []
|
|
2998
3120
|
return
|
package/package.json
CHANGED