@andespindola/brainlink 0.1.0-beta.90 → 0.1.0-beta.92
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 +58 -68
- 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 small connected 1000-note points, zoom-in delays expansion until the focused cluster is visually close, then 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,14 +23,20 @@ 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
|
|
30
|
-
const ecosystemClusterScaleThreshold = 0.
|
|
30
|
+
const ecosystemClusterScaleThreshold = 0.78
|
|
31
31
|
const ecosystemSubgraphScaleThreshold = 0.18
|
|
32
32
|
const ecosystemMicroScaleThreshold = 0.08
|
|
33
|
-
const ecosystemFocusedParentLimit =
|
|
33
|
+
const ecosystemFocusedParentLimit = 2
|
|
34
|
+
const ecosystemExpansionLevels = [
|
|
35
|
+
{ parentSize: 1000, childSize: 500, start: 0.16, end: 0.34 },
|
|
36
|
+
{ parentSize: 500, childSize: 250, start: 0.3, end: 0.5 },
|
|
37
|
+
{ parentSize: 250, childSize: 125, start: 0.46, end: 0.66 },
|
|
38
|
+
{ parentSize: 125, childSize: 60, start: 0.62, end: 0.78 }
|
|
39
|
+
]
|
|
34
40
|
const zoomRecoveryGuardMs = 4200
|
|
35
41
|
const zoomCapTargetViewportShare = 0.72
|
|
36
42
|
const meshEdgeScaleThreshold = 0.09
|
|
@@ -737,9 +743,11 @@ const selectEcosystemRepresentative = nodes => {
|
|
|
737
743
|
}
|
|
738
744
|
|
|
739
745
|
const ecosystemLayoutSpacingForSize = size => {
|
|
740
|
-
if (size >= 1000) return
|
|
741
|
-
if (size >=
|
|
742
|
-
return
|
|
746
|
+
if (size >= 1000) return 260
|
|
747
|
+
if (size >= 500) return 92
|
|
748
|
+
if (size >= 250) return 52
|
|
749
|
+
if (size >= 125) return 28
|
|
750
|
+
return 16
|
|
743
751
|
}
|
|
744
752
|
|
|
745
753
|
const ecosystemCompactPoint = (index, total, center, spacing) => {
|
|
@@ -886,7 +894,9 @@ 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 semanticZoomSpread = progress => Math.pow(progress, 2.6)
|
|
898
|
+
|
|
899
|
+
const opacityForSpread = spread => 0.03 + smoothStep(spread) * 0.97
|
|
890
900
|
|
|
891
901
|
const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
|
|
892
902
|
const focusPoint = ecosystemFocusPoint()
|
|
@@ -909,13 +919,17 @@ const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
|
|
|
909
919
|
|
|
910
920
|
const spreadChildClusterFromParent = (cluster, spread) => {
|
|
911
921
|
if (!Number.isFinite(cluster.parentX) || !Number.isFinite(cluster.parentY)) {
|
|
912
|
-
return
|
|
922
|
+
return {
|
|
923
|
+
...cluster,
|
|
924
|
+
lodOpacity: opacityForSpread(spread)
|
|
925
|
+
}
|
|
913
926
|
}
|
|
914
927
|
|
|
915
928
|
return {
|
|
916
929
|
...cluster,
|
|
917
930
|
x: cluster.parentX + (cluster.x - cluster.parentX) * spread,
|
|
918
|
-
y: cluster.parentY + (cluster.y - cluster.parentY) * spread
|
|
931
|
+
y: cluster.parentY + (cluster.y - cluster.parentY) * spread,
|
|
932
|
+
lodOpacity: opacityForSpread(spread)
|
|
919
933
|
}
|
|
920
934
|
}
|
|
921
935
|
|
|
@@ -923,61 +937,24 @@ const selectHierarchicalEcosystemClusters = viewport => {
|
|
|
923
937
|
const baseClusters = state.ecosystemClustersBySize.get(ecosystemGroupSize) ?? state.ecosystemClusters
|
|
924
938
|
const visibleBaseClusters = filterEcosystemClustersByViewport(baseClusters, viewport)
|
|
925
939
|
const hubClusters = state.ecosystemHubCluster ? [state.ecosystemHubCluster] : []
|
|
940
|
+
const visibleClusters = [...visibleBaseClusters]
|
|
926
941
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
]
|
|
942
|
+
for (let index = 0; index < ecosystemExpansionLevels.length; index += 1) {
|
|
943
|
+
const level = ecosystemExpansionLevels[index]
|
|
944
|
+
const parentClusters = visibleClusters.filter(cluster => cluster.size === level.parentSize)
|
|
945
|
+
if (parentClusters.length === 0) {
|
|
946
|
+
continue
|
|
947
|
+
}
|
|
948
|
+
const progress = zoomProgress(state.transform.scale, level.start, level.end)
|
|
949
|
+
if (progress <= 0.025) {
|
|
950
|
+
continue
|
|
951
|
+
}
|
|
952
|
+
const spread = semanticZoomSpread(progress)
|
|
953
|
+
const expansion = expandFocusedClusters(parentClusters, level.childSize, spread, viewport)
|
|
954
|
+
visibleClusters.push(...expansion.childClusters)
|
|
969
955
|
}
|
|
970
956
|
|
|
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
|
-
]
|
|
957
|
+
return [...hubClusters, ...visibleClusters]
|
|
981
958
|
}
|
|
982
959
|
|
|
983
960
|
const ecosystemSiblingEdgesForClusters = (clusters, existingEdges) => {
|
|
@@ -2553,15 +2530,18 @@ const clusterRadiusPx = cluster => {
|
|
|
2553
2530
|
return 10
|
|
2554
2531
|
}
|
|
2555
2532
|
if (cluster.isHub) {
|
|
2556
|
-
return
|
|
2533
|
+
return 4.2
|
|
2557
2534
|
}
|
|
2558
2535
|
if (String(cluster.id).startsWith('ecosystem-')) {
|
|
2559
|
-
const base = cluster.size >= 1000 ? 2.
|
|
2560
|
-
return Math.max(
|
|
2536
|
+
const base = cluster.size >= 1000 ? 1.2 : cluster.size >= 500 ? 1.1 : cluster.size >= 250 ? 1 : cluster.size >= 125 ? 0.92 : 0.86
|
|
2537
|
+
return Math.max(0.85, Math.min(2.45, base + Math.log10(cluster.count + 1) * 0.18))
|
|
2561
2538
|
}
|
|
2562
2539
|
return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
2563
2540
|
}
|
|
2564
2541
|
|
|
2542
|
+
const clusterOpacity = cluster =>
|
|
2543
|
+
Math.max(0, Math.min(1, Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1))
|
|
2544
|
+
|
|
2565
2545
|
const worldViewportBounds = () => {
|
|
2566
2546
|
const rect = canvas.getBoundingClientRect()
|
|
2567
2547
|
const width = Math.max(rect.width, 320)
|
|
@@ -2977,23 +2957,32 @@ const render = now => {
|
|
|
2977
2957
|
ctx.scale(state.transform.scale, state.transform.scale)
|
|
2978
2958
|
const safeScale = Math.max(state.transform.scale, 0.0001)
|
|
2979
2959
|
if (state.renderClusterEdges.length > 0) {
|
|
2980
|
-
ctx.beginPath()
|
|
2981
2960
|
for (let index = 0; index < state.renderClusterEdges.length; index += 1) {
|
|
2982
2961
|
const edge = state.renderClusterEdges[index]
|
|
2962
|
+
const edgeOpacity = Math.min(clusterOpacity(edge.sourceCluster), clusterOpacity(edge.targetCluster))
|
|
2963
|
+
if (edgeOpacity <= 0.01) {
|
|
2964
|
+
continue
|
|
2965
|
+
}
|
|
2966
|
+
ctx.beginPath()
|
|
2983
2967
|
ctx.moveTo(edge.sourceCluster.x, edge.sourceCluster.y)
|
|
2984
2968
|
ctx.lineTo(edge.targetCluster.x, edge.targetCluster.y)
|
|
2969
|
+
ctx.lineWidth = 1.2 / safeScale
|
|
2970
|
+
ctx.strokeStyle = 'rgba(153, 165, 181, ' + (edge.inferred ? 0.14 : 0.22) * edgeOpacity + ')'
|
|
2971
|
+
ctx.stroke()
|
|
2985
2972
|
}
|
|
2986
|
-
ctx.lineWidth = 1.2 / safeScale
|
|
2987
|
-
ctx.strokeStyle = 'rgba(153, 165, 181, 0.22)'
|
|
2988
|
-
ctx.stroke()
|
|
2989
2973
|
}
|
|
2990
2974
|
state.renderClusters.forEach(cluster => {
|
|
2991
2975
|
const isMacro = cluster.id === 'macro-galaxy'
|
|
2992
2976
|
const isEcosystem = String(cluster.id).startsWith('ecosystem-')
|
|
2993
2977
|
const isHub = Boolean(cluster.isHub)
|
|
2978
|
+
const opacity = clusterOpacity(cluster)
|
|
2979
|
+
if (opacity <= 0.01) {
|
|
2980
|
+
return
|
|
2981
|
+
}
|
|
2994
2982
|
const radiusPx = clusterRadiusPx(cluster)
|
|
2995
2983
|
const radius = radiusPx / safeScale
|
|
2996
2984
|
const haloRadius = (radiusPx + (isMacro ? 8 : isHub ? 4 : isEcosystem ? 1.1 : 4)) / safeScale
|
|
2985
|
+
ctx.globalAlpha = opacity
|
|
2997
2986
|
if (isHub || !isEcosystem || state.transform.scale >= ecosystemSubgraphScaleThreshold) {
|
|
2998
2987
|
ctx.beginPath()
|
|
2999
2988
|
ctx.arc(cluster.x, cluster.y, haloRadius, 0, Math.PI * 2)
|
|
@@ -3014,6 +3003,7 @@ const render = now => {
|
|
|
3014
3003
|
ctx.textBaseline = 'top'
|
|
3015
3004
|
ctx.fillText(cluster.representative.title.slice(0, 28), cluster.x, cluster.y + (radiusPx + 9) / safeScale)
|
|
3016
3005
|
}
|
|
3006
|
+
ctx.globalAlpha = 1
|
|
3017
3007
|
// Keep cluster markers minimal and faster to draw on large graphs.
|
|
3018
3008
|
})
|
|
3019
3009
|
ctx.restore()
|
package/package.json
CHANGED