@andespindola/brainlink 0.1.0-beta.90 → 0.1.0-beta.91
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 +48 -61
- 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 a compact memory-hub-centered mesh of connected 1000-note points, zoom-in spreads only focused clusters
|
|
605
|
+
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use a compact memory-hub-centered mesh of connected 1000-note points, zoom-in continuously spreads and fades only focused clusters through 500-note, 250-note, 125-note and 60-note subgraphs with aggregated real links plus local sibling mesh links, keeps parent points during expansion to avoid visual jumps, then progressively raises the focused node budget so local areas keep nearby notes and links visible
|
|
606
606
|
|
|
607
607
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
608
608
|
|
|
@@ -23,7 +23,7 @@ const transformCoordinateLimit = 20_000_000
|
|
|
23
23
|
const hoverHitTestIntervalMs = 64
|
|
24
24
|
const ecosystemGroupSize = 1000
|
|
25
25
|
const ecosystemActivationNodeThreshold = 1000
|
|
26
|
-
const ecosystemGroupSizes = [1000, 250, 60]
|
|
26
|
+
const ecosystemGroupSizes = [1000, 500, 250, 125, 60]
|
|
27
27
|
const ecosystemClusterEdgeLimit = 520
|
|
28
28
|
const ecosystemHubEdgeLimit = 120
|
|
29
29
|
const ecosystemSiblingEdgeLimit = 180
|
|
@@ -31,6 +31,12 @@ const ecosystemClusterScaleThreshold = 0.32
|
|
|
31
31
|
const ecosystemSubgraphScaleThreshold = 0.18
|
|
32
32
|
const ecosystemMicroScaleThreshold = 0.08
|
|
33
33
|
const ecosystemFocusedParentLimit = 3
|
|
34
|
+
const ecosystemExpansionLevels = [
|
|
35
|
+
{ parentSize: 1000, childSize: 500, start: 0.035, end: 0.105 },
|
|
36
|
+
{ parentSize: 500, childSize: 250, start: 0.075, end: 0.165 },
|
|
37
|
+
{ parentSize: 250, childSize: 125, start: 0.135, end: 0.245 },
|
|
38
|
+
{ parentSize: 125, childSize: 60, start: 0.205, end: 0.32 }
|
|
39
|
+
]
|
|
34
40
|
const zoomRecoveryGuardMs = 4200
|
|
35
41
|
const zoomCapTargetViewportShare = 0.72
|
|
36
42
|
const meshEdgeScaleThreshold = 0.09
|
|
@@ -738,7 +744,9 @@ const selectEcosystemRepresentative = nodes => {
|
|
|
738
744
|
|
|
739
745
|
const ecosystemLayoutSpacingForSize = size => {
|
|
740
746
|
if (size >= 1000) return 360
|
|
747
|
+
if (size >= 500) return 160
|
|
741
748
|
if (size >= 250) return 92
|
|
749
|
+
if (size >= 125) return 48
|
|
742
750
|
return 28
|
|
743
751
|
}
|
|
744
752
|
|
|
@@ -886,7 +894,7 @@ const smoothStep = value => {
|
|
|
886
894
|
const zoomProgress = (scale, start, end) =>
|
|
887
895
|
smoothStep((scale - start) / Math.max(end - start, 0.0001))
|
|
888
896
|
|
|
889
|
-
const
|
|
897
|
+
const opacityForSpread = spread => 0.05 + smoothStep(spread) * 0.95
|
|
890
898
|
|
|
891
899
|
const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
|
|
892
900
|
const focusPoint = ecosystemFocusPoint()
|
|
@@ -909,13 +917,17 @@ const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
|
|
|
909
917
|
|
|
910
918
|
const spreadChildClusterFromParent = (cluster, spread) => {
|
|
911
919
|
if (!Number.isFinite(cluster.parentX) || !Number.isFinite(cluster.parentY)) {
|
|
912
|
-
return
|
|
920
|
+
return {
|
|
921
|
+
...cluster,
|
|
922
|
+
lodOpacity: opacityForSpread(spread)
|
|
923
|
+
}
|
|
913
924
|
}
|
|
914
925
|
|
|
915
926
|
return {
|
|
916
927
|
...cluster,
|
|
917
928
|
x: cluster.parentX + (cluster.x - cluster.parentX) * spread,
|
|
918
|
-
y: cluster.parentY + (cluster.y - cluster.parentY) * spread
|
|
929
|
+
y: cluster.parentY + (cluster.y - cluster.parentY) * spread,
|
|
930
|
+
lodOpacity: opacityForSpread(spread)
|
|
919
931
|
}
|
|
920
932
|
}
|
|
921
933
|
|
|
@@ -923,61 +935,23 @@ const selectHierarchicalEcosystemClusters = viewport => {
|
|
|
923
935
|
const baseClusters = state.ecosystemClustersBySize.get(ecosystemGroupSize) ?? state.ecosystemClusters
|
|
924
936
|
const visibleBaseClusters = filterEcosystemClustersByViewport(baseClusters, viewport)
|
|
925
937
|
const hubClusters = state.ecosystemHubCluster ? [state.ecosystemHubCluster] : []
|
|
938
|
+
const visibleClusters = [...visibleBaseClusters]
|
|
926
939
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
viewport
|
|
938
|
-
|
|
939
|
-
if (midExpansion.childClusters.length === 0) {
|
|
940
|
-
return [...hubClusters, ...visibleBaseClusters]
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const midSpread = state.transform.scale <= ecosystemSubgraphScaleThreshold
|
|
944
|
-
? zoomProgress(state.transform.scale, ecosystemMicroScaleThreshold, ecosystemSubgraphScaleThreshold)
|
|
945
|
-
: 1
|
|
946
|
-
const remainingBaseClusters = shouldReplaceParentCluster(midSpread)
|
|
947
|
-
? visibleBaseClusters.filter(cluster => !midExpansion.expandedParentIds.has(cluster.id))
|
|
948
|
-
: visibleBaseClusters
|
|
949
|
-
if (state.transform.scale <= ecosystemSubgraphScaleThreshold) {
|
|
950
|
-
return [
|
|
951
|
-
...hubClusters,
|
|
952
|
-
...remainingBaseClusters,
|
|
953
|
-
...midExpansion.childClusters
|
|
954
|
-
]
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
const microExpansion = expandFocusedClusters(
|
|
958
|
-
midExpansion.childClusters,
|
|
959
|
-
60,
|
|
960
|
-
zoomProgress(state.transform.scale, ecosystemSubgraphScaleThreshold, ecosystemClusterScaleThreshold),
|
|
961
|
-
viewport
|
|
962
|
-
)
|
|
963
|
-
if (microExpansion.childClusters.length === 0) {
|
|
964
|
-
return [
|
|
965
|
-
...hubClusters,
|
|
966
|
-
...remainingBaseClusters,
|
|
967
|
-
...midExpansion.childClusters
|
|
968
|
-
]
|
|
940
|
+
for (let index = 0; index < ecosystemExpansionLevels.length; index += 1) {
|
|
941
|
+
const level = ecosystemExpansionLevels[index]
|
|
942
|
+
const parentClusters = visibleClusters.filter(cluster => cluster.size === level.parentSize)
|
|
943
|
+
if (parentClusters.length === 0) {
|
|
944
|
+
continue
|
|
945
|
+
}
|
|
946
|
+
const spread = zoomProgress(state.transform.scale, level.start, level.end)
|
|
947
|
+
if (spread <= 0.015) {
|
|
948
|
+
continue
|
|
949
|
+
}
|
|
950
|
+
const expansion = expandFocusedClusters(parentClusters, level.childSize, spread, viewport)
|
|
951
|
+
visibleClusters.push(...expansion.childClusters)
|
|
969
952
|
}
|
|
970
953
|
|
|
971
|
-
|
|
972
|
-
const visibleMidClusters = shouldReplaceParentCluster(microSpread)
|
|
973
|
-
? midExpansion.childClusters.filter(cluster => !microExpansion.expandedParentIds.has(cluster.id))
|
|
974
|
-
: midExpansion.childClusters
|
|
975
|
-
return [
|
|
976
|
-
...hubClusters,
|
|
977
|
-
...remainingBaseClusters,
|
|
978
|
-
...visibleMidClusters,
|
|
979
|
-
...microExpansion.childClusters
|
|
980
|
-
]
|
|
954
|
+
return [...hubClusters, ...visibleClusters]
|
|
981
955
|
}
|
|
982
956
|
|
|
983
957
|
const ecosystemSiblingEdgesForClusters = (clusters, existingEdges) => {
|
|
@@ -2556,12 +2530,15 @@ const clusterRadiusPx = cluster => {
|
|
|
2556
2530
|
return 5.2
|
|
2557
2531
|
}
|
|
2558
2532
|
if (String(cluster.id).startsWith('ecosystem-')) {
|
|
2559
|
-
const base = cluster.size >= 1000 ? 2.4 : cluster.size >= 250 ? 2.1 : 1.8
|
|
2533
|
+
const base = cluster.size >= 1000 ? 2.4 : cluster.size >= 500 ? 2.25 : cluster.size >= 250 ? 2.1 : cluster.size >= 125 ? 1.95 : 1.8
|
|
2560
2534
|
return Math.max(1.8, Math.min(4.2, base + Math.log10(cluster.count + 1) * 0.28))
|
|
2561
2535
|
}
|
|
2562
2536
|
return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
2563
2537
|
}
|
|
2564
2538
|
|
|
2539
|
+
const clusterOpacity = cluster =>
|
|
2540
|
+
Math.max(0, Math.min(1, Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1))
|
|
2541
|
+
|
|
2565
2542
|
const worldViewportBounds = () => {
|
|
2566
2543
|
const rect = canvas.getBoundingClientRect()
|
|
2567
2544
|
const width = Math.max(rect.width, 320)
|
|
@@ -2977,23 +2954,32 @@ const render = now => {
|
|
|
2977
2954
|
ctx.scale(state.transform.scale, state.transform.scale)
|
|
2978
2955
|
const safeScale = Math.max(state.transform.scale, 0.0001)
|
|
2979
2956
|
if (state.renderClusterEdges.length > 0) {
|
|
2980
|
-
ctx.beginPath()
|
|
2981
2957
|
for (let index = 0; index < state.renderClusterEdges.length; index += 1) {
|
|
2982
2958
|
const edge = state.renderClusterEdges[index]
|
|
2959
|
+
const edgeOpacity = Math.min(clusterOpacity(edge.sourceCluster), clusterOpacity(edge.targetCluster))
|
|
2960
|
+
if (edgeOpacity <= 0.01) {
|
|
2961
|
+
continue
|
|
2962
|
+
}
|
|
2963
|
+
ctx.beginPath()
|
|
2983
2964
|
ctx.moveTo(edge.sourceCluster.x, edge.sourceCluster.y)
|
|
2984
2965
|
ctx.lineTo(edge.targetCluster.x, edge.targetCluster.y)
|
|
2966
|
+
ctx.lineWidth = 1.2 / safeScale
|
|
2967
|
+
ctx.strokeStyle = 'rgba(153, 165, 181, ' + (edge.inferred ? 0.14 : 0.22) * edgeOpacity + ')'
|
|
2968
|
+
ctx.stroke()
|
|
2985
2969
|
}
|
|
2986
|
-
ctx.lineWidth = 1.2 / safeScale
|
|
2987
|
-
ctx.strokeStyle = 'rgba(153, 165, 181, 0.22)'
|
|
2988
|
-
ctx.stroke()
|
|
2989
2970
|
}
|
|
2990
2971
|
state.renderClusters.forEach(cluster => {
|
|
2991
2972
|
const isMacro = cluster.id === 'macro-galaxy'
|
|
2992
2973
|
const isEcosystem = String(cluster.id).startsWith('ecosystem-')
|
|
2993
2974
|
const isHub = Boolean(cluster.isHub)
|
|
2975
|
+
const opacity = clusterOpacity(cluster)
|
|
2976
|
+
if (opacity <= 0.01) {
|
|
2977
|
+
return
|
|
2978
|
+
}
|
|
2994
2979
|
const radiusPx = clusterRadiusPx(cluster)
|
|
2995
2980
|
const radius = radiusPx / safeScale
|
|
2996
2981
|
const haloRadius = (radiusPx + (isMacro ? 8 : isHub ? 4 : isEcosystem ? 1.1 : 4)) / safeScale
|
|
2982
|
+
ctx.globalAlpha = opacity
|
|
2997
2983
|
if (isHub || !isEcosystem || state.transform.scale >= ecosystemSubgraphScaleThreshold) {
|
|
2998
2984
|
ctx.beginPath()
|
|
2999
2985
|
ctx.arc(cluster.x, cluster.y, haloRadius, 0, Math.PI * 2)
|
|
@@ -3014,6 +3000,7 @@ const render = now => {
|
|
|
3014
3000
|
ctx.textBaseline = 'top'
|
|
3015
3001
|
ctx.fillText(cluster.representative.title.slice(0, 28), cluster.x, cluster.y + (radiusPx + 9) / safeScale)
|
|
3016
3002
|
}
|
|
3003
|
+
ctx.globalAlpha = 1
|
|
3017
3004
|
// Keep cluster markers minimal and faster to draw on large graphs.
|
|
3018
3005
|
})
|
|
3019
3006
|
ctx.restore()
|
package/package.json
CHANGED