@andespindola/brainlink 0.1.0-beta.95 → 0.1.0-beta.97
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 +111 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -602,7 +602,7 @@ The graph UI shows:
|
|
|
602
602
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
603
603
|
- compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
|
|
604
604
|
- 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
|
|
605
|
-
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use
|
|
605
|
+
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use one recursive model where each visible level targets up to 999 non-hub nodes, starts from a memory-hub-centered mesh, and each supernode can expand into another same-shape subgraph level (again up to 999 children) with latent fade-in, aggregated real links and local sibling mesh links so org-heavy and stress-50k follow the same structure at different depths
|
|
606
606
|
|
|
607
607
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
608
608
|
|
|
@@ -21,9 +21,8 @@ const viewportPaddingPx = 280
|
|
|
21
21
|
const worldCoordinateLimit = 5_000_000
|
|
22
22
|
const transformCoordinateLimit = 20_000_000
|
|
23
23
|
const hoverHitTestIntervalMs = 64
|
|
24
|
-
const
|
|
24
|
+
const ecosystemLevelNodeCap = 999
|
|
25
25
|
const ecosystemActivationNodeThreshold = 1000
|
|
26
|
-
const ecosystemGroupSizes = [1000, 500, 250, 125, 60, 30, 15, 8]
|
|
27
26
|
const ecosystemClusterEdgeLimit = 520
|
|
28
27
|
const ecosystemHubEdgeLimit = 120
|
|
29
28
|
const ecosystemSiblingEdgeLimit = 180
|
|
@@ -32,15 +31,6 @@ const massiveEcosystemClusterScaleThreshold = 4.2
|
|
|
32
31
|
const ecosystemSubgraphScaleThreshold = 0.18
|
|
33
32
|
const ecosystemMicroScaleThreshold = 0.08
|
|
34
33
|
const ecosystemFocusedParentLimit = 2
|
|
35
|
-
const ecosystemExpansionLevels = [
|
|
36
|
-
{ parentSize: 1000, childSize: 500, start: 0.04, end: 0.62 },
|
|
37
|
-
{ parentSize: 500, childSize: 250, start: 0.16, end: 0.72 },
|
|
38
|
-
{ parentSize: 250, childSize: 125, start: 0.3, end: 0.78 },
|
|
39
|
-
{ parentSize: 125, childSize: 60, start: 0.46, end: 0.86 },
|
|
40
|
-
{ parentSize: 60, childSize: 30, start: 0.64, end: 1.06 },
|
|
41
|
-
{ parentSize: 30, childSize: 15, start: 0.84, end: 1.38 },
|
|
42
|
-
{ parentSize: 15, childSize: 8, start: 1.08, end: 1.82 }
|
|
43
|
-
]
|
|
44
34
|
const zoomRecoveryGuardMs = 4200
|
|
45
35
|
const zoomCapTargetViewportShare = 0.72
|
|
46
36
|
const meshEdgeScaleThreshold = 0.09
|
|
@@ -87,6 +77,9 @@ const state = {
|
|
|
87
77
|
ecosystemClusters: [],
|
|
88
78
|
ecosystemClustersBySize: new Map(),
|
|
89
79
|
ecosystemNodeClusterBySize: new Map(),
|
|
80
|
+
ecosystemLevelSizes: [],
|
|
81
|
+
ecosystemExpansionLevels: [],
|
|
82
|
+
ecosystemBaseSize: ecosystemLevelNodeCap,
|
|
90
83
|
ecosystemHubCluster: null,
|
|
91
84
|
macroCenter: { x: 0, y: 0 },
|
|
92
85
|
macroRepresentative: null,
|
|
@@ -597,10 +590,21 @@ const recomputeVisibility = () => {
|
|
|
597
590
|
: { x: 0, y: 0 }
|
|
598
591
|
const ecosystemGraph = nodes.length > ecosystemActivationNodeThreshold
|
|
599
592
|
? buildEcosystemGraph(nodes, state.macroCenter, primaryHub)
|
|
600
|
-
: {
|
|
593
|
+
: {
|
|
594
|
+
clusters: [],
|
|
595
|
+
clustersBySize: new Map(),
|
|
596
|
+
nodeClusterBySize: new Map(),
|
|
597
|
+
levelSizes: [],
|
|
598
|
+
expansionLevels: [],
|
|
599
|
+
baseSize: ecosystemLevelNodeCap,
|
|
600
|
+
hubCluster: null
|
|
601
|
+
}
|
|
601
602
|
state.ecosystemClusters = ecosystemGraph.clusters
|
|
602
603
|
state.ecosystemClustersBySize = ecosystemGraph.clustersBySize
|
|
603
604
|
state.ecosystemNodeClusterBySize = ecosystemGraph.nodeClusterBySize
|
|
605
|
+
state.ecosystemLevelSizes = ecosystemGraph.levelSizes
|
|
606
|
+
state.ecosystemExpansionLevels = ecosystemGraph.expansionLevels
|
|
607
|
+
state.ecosystemBaseSize = ecosystemGraph.baseSize
|
|
604
608
|
state.ecosystemHubCluster = ecosystemGraph.hubCluster
|
|
605
609
|
state.macroRepresentative = resolveMacroRepresentative(nodes)
|
|
606
610
|
markRenderDirty()
|
|
@@ -747,14 +751,56 @@ const selectEcosystemRepresentative = nodes => {
|
|
|
747
751
|
}
|
|
748
752
|
|
|
749
753
|
const ecosystemLayoutSpacingForSize = size => {
|
|
750
|
-
if (size >=
|
|
751
|
-
if (size >=
|
|
752
|
-
if (size >=
|
|
753
|
-
if (size >=
|
|
754
|
-
if (size >=
|
|
755
|
-
if (size >=
|
|
756
|
-
|
|
757
|
-
|
|
754
|
+
if (size >= ecosystemLevelNodeCap) return 260
|
|
755
|
+
if (size >= 320) return 110
|
|
756
|
+
if (size >= 120) return 64
|
|
757
|
+
if (size >= 48) return 34
|
|
758
|
+
if (size >= 18) return 18
|
|
759
|
+
if (size >= 8) return 11
|
|
760
|
+
return 7
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const buildEcosystemLevelSizes = nodeCount => {
|
|
764
|
+
if (nodeCount <= 0) return []
|
|
765
|
+
const sizes = []
|
|
766
|
+
let currentSize = Math.max(1, Math.ceil(nodeCount / ecosystemLevelNodeCap))
|
|
767
|
+
while (currentSize >= 1) {
|
|
768
|
+
sizes.push(currentSize)
|
|
769
|
+
if (currentSize === 1) {
|
|
770
|
+
break
|
|
771
|
+
}
|
|
772
|
+
const nextSize = Math.max(1, Math.ceil(currentSize / ecosystemLevelNodeCap))
|
|
773
|
+
if (nextSize === currentSize) {
|
|
774
|
+
break
|
|
775
|
+
}
|
|
776
|
+
currentSize = nextSize
|
|
777
|
+
}
|
|
778
|
+
return sizes
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const buildEcosystemExpansionLevels = (levelSizes, nodeCount) => {
|
|
782
|
+
if (levelSizes.length <= 1) {
|
|
783
|
+
return []
|
|
784
|
+
}
|
|
785
|
+
const maxScale = nodeCount > massiveGraphNodeThreshold
|
|
786
|
+
? massiveEcosystemClusterScaleThreshold
|
|
787
|
+
: ecosystemClusterScaleThreshold
|
|
788
|
+
const startScale = 0.04
|
|
789
|
+
const transitionCount = levelSizes.length - 1
|
|
790
|
+
const usableScale = Math.max(0.08, maxScale - startScale)
|
|
791
|
+
const step = usableScale / transitionCount
|
|
792
|
+
const levels = []
|
|
793
|
+
for (let index = 0; index < transitionCount; index += 1) {
|
|
794
|
+
const start = startScale + step * index * 0.72
|
|
795
|
+
const end = Math.min(maxScale, start + step * 1.85)
|
|
796
|
+
levels.push({
|
|
797
|
+
parentSize: levelSizes[index],
|
|
798
|
+
childSize: levelSizes[index + 1],
|
|
799
|
+
start,
|
|
800
|
+
end
|
|
801
|
+
})
|
|
802
|
+
}
|
|
803
|
+
return levels
|
|
758
804
|
}
|
|
759
805
|
|
|
760
806
|
const ecosystemCompactPoint = (index, total, center, spacing) => {
|
|
@@ -837,19 +883,30 @@ const buildEcosystemLevel = (sortedNodes, size, parentLookup, center) => {
|
|
|
837
883
|
|
|
838
884
|
const buildEcosystemGraph = (nodes, center, hub) => {
|
|
839
885
|
if (nodes.length === 0) {
|
|
840
|
-
return {
|
|
886
|
+
return {
|
|
887
|
+
clusters: [],
|
|
888
|
+
clustersBySize: new Map(),
|
|
889
|
+
nodeClusterBySize: new Map(),
|
|
890
|
+
levelSizes: [],
|
|
891
|
+
expansionLevels: [],
|
|
892
|
+
baseSize: ecosystemLevelNodeCap,
|
|
893
|
+
hubCluster: null
|
|
894
|
+
}
|
|
841
895
|
}
|
|
842
896
|
|
|
843
897
|
const hubCluster = buildEcosystemHubCluster(hub, center)
|
|
844
898
|
const sortedNodes = nodes
|
|
845
899
|
.filter(node => node.id !== hub?.id)
|
|
846
900
|
.sort(compareNodesForEcosystem)
|
|
901
|
+
const levelSizes = buildEcosystemLevelSizes(sortedNodes.length)
|
|
902
|
+
const expansionLevels = buildEcosystemExpansionLevels(levelSizes, nodes.length)
|
|
903
|
+
const baseSize = levelSizes[0] ?? ecosystemLevelNodeCap
|
|
847
904
|
const clustersBySize = new Map()
|
|
848
905
|
const nodeClusterBySize = new Map()
|
|
849
906
|
let parentLookup = null
|
|
850
907
|
|
|
851
|
-
for (let index = 0; index <
|
|
852
|
-
const size =
|
|
908
|
+
for (let index = 0; index < levelSizes.length; index += 1) {
|
|
909
|
+
const size = levelSizes[index]
|
|
853
910
|
const level = buildEcosystemLevel(sortedNodes, size, parentLookup, center)
|
|
854
911
|
clustersBySize.set(size, level.clusters)
|
|
855
912
|
nodeClusterBySize.set(size, level.clusterByNodeId)
|
|
@@ -857,9 +914,12 @@ const buildEcosystemGraph = (nodes, center, hub) => {
|
|
|
857
914
|
}
|
|
858
915
|
|
|
859
916
|
return {
|
|
860
|
-
clusters: clustersBySize.get(
|
|
917
|
+
clusters: clustersBySize.get(baseSize) ?? [],
|
|
861
918
|
clustersBySize,
|
|
862
919
|
nodeClusterBySize,
|
|
920
|
+
levelSizes,
|
|
921
|
+
expansionLevels,
|
|
922
|
+
baseSize,
|
|
863
923
|
hubCluster
|
|
864
924
|
}
|
|
865
925
|
}
|
|
@@ -901,9 +961,21 @@ const smoothStep = value => {
|
|
|
901
961
|
const zoomProgress = (scale, start, end) =>
|
|
902
962
|
smoothStep((scale - start) / Math.max(end - start, 0.0001))
|
|
903
963
|
|
|
904
|
-
const semanticZoomSpread =
|
|
964
|
+
const semanticZoomSpread = (progress, childSize) => {
|
|
965
|
+
const curve = Math.pow(progress, 4.2)
|
|
966
|
+
if (childSize >= Math.ceil(ecosystemLevelNodeCap / 2)) {
|
|
967
|
+
return 0.12 + curve * 0.88
|
|
968
|
+
}
|
|
969
|
+
return curve
|
|
970
|
+
}
|
|
905
971
|
|
|
906
|
-
const opacityForProgress =
|
|
972
|
+
const opacityForProgress = (progress, childSize) => {
|
|
973
|
+
const eased = Math.pow(progress, 2.1)
|
|
974
|
+
if (childSize >= Math.ceil(ecosystemLevelNodeCap / 2)) {
|
|
975
|
+
return 0.22 + eased * 0.78
|
|
976
|
+
}
|
|
977
|
+
return eased
|
|
978
|
+
}
|
|
907
979
|
|
|
908
980
|
const expandFocusedClusters = (parentClusters, childSize, progress, spread, viewport) => {
|
|
909
981
|
const focusPoint = ecosystemFocusPoint()
|
|
@@ -915,7 +987,7 @@ const expandFocusedClusters = (parentClusters, childSize, progress, spread, view
|
|
|
915
987
|
const childClusters = state.ecosystemClustersBySize.get(childSize) ?? []
|
|
916
988
|
const visibleChildClusters = childClusters
|
|
917
989
|
.filter(cluster => expandedParentIds.has(cluster.parentId))
|
|
918
|
-
.map(cluster => spreadChildClusterFromParent(cluster, progress, spread))
|
|
990
|
+
.map(cluster => spreadChildClusterFromParent(cluster, childSize, progress, spread))
|
|
919
991
|
.filter(cluster => isClusterInViewport(cluster, viewport))
|
|
920
992
|
|
|
921
993
|
return {
|
|
@@ -924,11 +996,11 @@ const expandFocusedClusters = (parentClusters, childSize, progress, spread, view
|
|
|
924
996
|
}
|
|
925
997
|
}
|
|
926
998
|
|
|
927
|
-
const spreadChildClusterFromParent = (cluster, progress, spread) => {
|
|
999
|
+
const spreadChildClusterFromParent = (cluster, childSize, progress, spread) => {
|
|
928
1000
|
if (!Number.isFinite(cluster.parentX) || !Number.isFinite(cluster.parentY)) {
|
|
929
1001
|
return {
|
|
930
1002
|
...cluster,
|
|
931
|
-
lodOpacity: opacityForProgress(progress)
|
|
1003
|
+
lodOpacity: opacityForProgress(progress, childSize)
|
|
932
1004
|
}
|
|
933
1005
|
}
|
|
934
1006
|
|
|
@@ -936,24 +1008,24 @@ const spreadChildClusterFromParent = (cluster, progress, spread) => {
|
|
|
936
1008
|
...cluster,
|
|
937
1009
|
x: cluster.parentX + (cluster.x - cluster.parentX) * spread,
|
|
938
1010
|
y: cluster.parentY + (cluster.y - cluster.parentY) * spread,
|
|
939
|
-
lodOpacity: opacityForProgress(progress)
|
|
1011
|
+
lodOpacity: opacityForProgress(progress, childSize)
|
|
940
1012
|
}
|
|
941
1013
|
}
|
|
942
1014
|
|
|
943
1015
|
const selectHierarchicalEcosystemClusters = viewport => {
|
|
944
|
-
const baseClusters = state.ecosystemClustersBySize.get(
|
|
1016
|
+
const baseClusters = state.ecosystemClustersBySize.get(state.ecosystemBaseSize) ?? state.ecosystemClusters
|
|
945
1017
|
const visibleBaseClusters = filterEcosystemClustersByViewport(baseClusters, viewport)
|
|
946
1018
|
const hubClusters = state.ecosystemHubCluster ? [state.ecosystemHubCluster] : []
|
|
947
1019
|
const visibleClusters = [...visibleBaseClusters]
|
|
948
1020
|
|
|
949
|
-
for (let index = 0; index < ecosystemExpansionLevels.length; index += 1) {
|
|
950
|
-
const level = ecosystemExpansionLevels[index]
|
|
1021
|
+
for (let index = 0; index < state.ecosystemExpansionLevels.length; index += 1) {
|
|
1022
|
+
const level = state.ecosystemExpansionLevels[index]
|
|
951
1023
|
const parentClusters = visibleClusters.filter(cluster => cluster.size === level.parentSize)
|
|
952
1024
|
if (parentClusters.length === 0) {
|
|
953
1025
|
continue
|
|
954
1026
|
}
|
|
955
1027
|
const progress = zoomProgress(state.transform.scale, level.start, level.end)
|
|
956
|
-
const spread = semanticZoomSpread(progress)
|
|
1028
|
+
const spread = semanticZoomSpread(progress, level.childSize)
|
|
957
1029
|
const expansion = expandFocusedClusters(parentClusters, level.childSize, progress, spread, viewport)
|
|
958
1030
|
visibleClusters.push(...expansion.childClusters)
|
|
959
1031
|
}
|
|
@@ -2535,25 +2607,13 @@ const clusterRadiusPx = cluster => {
|
|
|
2535
2607
|
return 10
|
|
2536
2608
|
}
|
|
2537
2609
|
if (cluster.isHub) {
|
|
2538
|
-
return 3.
|
|
2610
|
+
return 3.8
|
|
2539
2611
|
}
|
|
2540
2612
|
if (String(cluster.id).startsWith('ecosystem-')) {
|
|
2541
|
-
const
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
: cluster.size >= 250
|
|
2546
|
-
? 0.56
|
|
2547
|
-
: cluster.size >= 125
|
|
2548
|
-
? 0.52
|
|
2549
|
-
: cluster.size >= 60
|
|
2550
|
-
? 0.5
|
|
2551
|
-
: cluster.size >= 30
|
|
2552
|
-
? 0.58
|
|
2553
|
-
: cluster.size >= 15
|
|
2554
|
-
? 0.72
|
|
2555
|
-
: 0.86
|
|
2556
|
-
return Math.max(0.48, Math.min(1.75, base + Math.log10(cluster.count + 1) * 0.11))
|
|
2613
|
+
const size = Math.max(1, Math.min(ecosystemLevelNodeCap, cluster.size || cluster.count || 1))
|
|
2614
|
+
const sizeBias = 0.56 + Math.log10(size + 1) * 0.28
|
|
2615
|
+
const densityBias = Math.log10((cluster.count || 1) + 1) * 0.12
|
|
2616
|
+
return Math.max(0.62, Math.min(2.4, sizeBias + densityBias))
|
|
2557
2617
|
}
|
|
2558
2618
|
return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
2559
2619
|
}
|
package/package.json
CHANGED