@andespindola/brainlink 0.1.0-beta.113 → 0.1.0-beta.114
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 -0
- package/dist/application/frontend/client-js.js +96 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -596,6 +596,7 @@ The graph UI shows:
|
|
|
596
596
|
- graph controls for zoom in, zoom out, fit visible nodes and reset-to-fit-all
|
|
597
597
|
- wheel zoom (including `cmd+scroll` and `ctrl+scroll`) anchored to cursor position for faster navigation in large graphs
|
|
598
598
|
- continuous target-scale interpolation for wheel/button zoom to avoid abrupt LOD jumps while keeping cursor-anchored focus
|
|
599
|
+
- bloom-like navigation profile: wheel zoom now biases focus toward hovered/selected/hub nodes and applies subtle camera-anchor parallax so depth remains stable while zooming and panning
|
|
599
600
|
- zoom-out floor for large and massive graphs, plus reset macro floor tied to hub-neighbor distance and first-level cluster spacing, with a tighter initial camera so the first graph appears as a closer cell instead of a distant mesh
|
|
600
601
|
- keyboard shortcuts: `+` zoom in, `-` zoom out, `0` reset fit
|
|
601
602
|
- double-click on canvas zooms in at cursor position
|
|
@@ -77,6 +77,11 @@ const zoomAnimationSlowLerp = 0.18
|
|
|
77
77
|
const zoomAnimationFastLerp = 0.36
|
|
78
78
|
const zoomAnimationScaleSnap = 0.00008
|
|
79
79
|
const zoomAnimationPositionSnap = 0.14
|
|
80
|
+
const bloomZoomFocusMaxScreenDistance = 260
|
|
81
|
+
const bloomZoomFocusFarStrength = 0.66
|
|
82
|
+
const bloomZoomFocusNearStrength = 0.34
|
|
83
|
+
const bloomCameraParallaxNearStrength = 0.2
|
|
84
|
+
const bloomCameraParallaxFarStrength = 0.06
|
|
80
85
|
const physicsDragFrameIntervalMs = 16
|
|
81
86
|
const physicsIdleFrameIntervalMs = 78
|
|
82
87
|
const physicsLargeGraphIdleFrameIntervalMs = 108
|
|
@@ -1270,19 +1275,20 @@ const projectEcosystemPoint = (x, y, depth, anchor) => {
|
|
|
1270
1275
|
|
|
1271
1276
|
const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
|
|
1272
1277
|
const levelIndexMap = ecosystemLevelIndexBySize()
|
|
1278
|
+
const effectiveAnchor = applyBloomCameraParallax(anchor)
|
|
1273
1279
|
const projectedClusters = []
|
|
1274
1280
|
const clusterById = new Map()
|
|
1275
1281
|
|
|
1276
1282
|
for (let index = 0; index < clusters.length; index += 1) {
|
|
1277
1283
|
const cluster = clusters[index]
|
|
1278
1284
|
const baseDepth = ecosystemDepthForCluster(cluster, levelIndexMap)
|
|
1279
|
-
const radialDistance = Math.hypot(cluster.x -
|
|
1285
|
+
const radialDistance = Math.hypot(cluster.x - effectiveAnchor.x, cluster.y - effectiveAnchor.y)
|
|
1280
1286
|
const radialOffset = cluster.isHub ? 0 : Math.min(320, radialDistance * ecosystemDepthRadialGain)
|
|
1281
1287
|
const orbitalOffset = cluster.isHub
|
|
1282
1288
|
? 0
|
|
1283
|
-
: Math.sin(Math.atan2(cluster.y -
|
|
1289
|
+
: Math.sin(Math.atan2(cluster.y - effectiveAnchor.y, cluster.x - effectiveAnchor.x) * 2.2) * ecosystemDepthOrbitalMaxOffset
|
|
1284
1290
|
const depth = Math.max(0, baseDepth + radialOffset + orbitalOffset)
|
|
1285
|
-
const projected = projectEcosystemPoint(cluster.x, cluster.y, depth,
|
|
1291
|
+
const projected = projectEcosystemPoint(cluster.x, cluster.y, depth, effectiveAnchor)
|
|
1286
1292
|
const baseOpacity = Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1
|
|
1287
1293
|
const depthScale = ecosystemDepthMinScale + (1 - ecosystemDepthMinScale) * projected.factor
|
|
1288
1294
|
const depthOpacity = Math.max(
|
|
@@ -1614,6 +1620,91 @@ const cursorWorldPoint = () => {
|
|
|
1614
1620
|
return screenToWorldPoint(screenX, screenY)
|
|
1615
1621
|
}
|
|
1616
1622
|
|
|
1623
|
+
const bloomZoomFocusStrength = (scale) => {
|
|
1624
|
+
if (scale <= 0.08) return bloomZoomFocusFarStrength
|
|
1625
|
+
if (scale <= 0.2) return 0.58
|
|
1626
|
+
if (scale <= 0.45) return 0.5
|
|
1627
|
+
if (scale <= 0.9) return 0.42
|
|
1628
|
+
return bloomZoomFocusNearStrength
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const bloomCameraParallaxStrength = (scale) => {
|
|
1632
|
+
if (scale <= 0.08) return bloomCameraParallaxNearStrength
|
|
1633
|
+
if (scale <= 0.2) return 0.16
|
|
1634
|
+
if (scale <= 0.45) return 0.12
|
|
1635
|
+
return bloomCameraParallaxFarStrength
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const screenPointForWorld = (worldX, worldY) => ({
|
|
1639
|
+
x: worldX * state.transform.scale + state.transform.x,
|
|
1640
|
+
y: worldY * state.transform.scale + state.transform.y
|
|
1641
|
+
})
|
|
1642
|
+
|
|
1643
|
+
const bloomZoomFocusCandidate = (screenX, screenY) => {
|
|
1644
|
+
const candidates = [state.hovered, state.selected, state.primaryHub].filter(Boolean)
|
|
1645
|
+
if (candidates.length === 0) {
|
|
1646
|
+
return null
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
let bestNode = null
|
|
1650
|
+
let bestDistance = Number.POSITIVE_INFINITY
|
|
1651
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
1652
|
+
const node = candidates[index]
|
|
1653
|
+
const point = screenPointForWorld(nodeRenderX(node), nodeRenderY(node))
|
|
1654
|
+
const distance = Math.hypot(point.x - screenX, point.y - screenY)
|
|
1655
|
+
if (distance < bestDistance) {
|
|
1656
|
+
bestDistance = distance
|
|
1657
|
+
bestNode = node
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (!bestNode) {
|
|
1662
|
+
return null
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
const scale = state.transform.scale
|
|
1666
|
+
const allowDistance = scale <= 0.16 ? bloomZoomFocusMaxScreenDistance * 1.7 : bloomZoomFocusMaxScreenDistance
|
|
1667
|
+
if (bestDistance > allowDistance) {
|
|
1668
|
+
return null
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
return {
|
|
1672
|
+
x: nodeRenderX(bestNode),
|
|
1673
|
+
y: nodeRenderY(bestNode)
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
const resolveZoomAnchorWorldPoint = (screenX, screenY, source) => {
|
|
1678
|
+
const cursorPoint = screenToWorldPoint(screenX, screenY)
|
|
1679
|
+
if (source !== 'wheel') {
|
|
1680
|
+
return cursorPoint
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
const focusCandidate = bloomZoomFocusCandidate(screenX, screenY)
|
|
1684
|
+
if (!focusCandidate) {
|
|
1685
|
+
return cursorPoint
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
const strength = bloomZoomFocusStrength(state.transform.scale)
|
|
1689
|
+
return {
|
|
1690
|
+
x: cursorPoint.x + (focusCandidate.x - cursorPoint.x) * strength,
|
|
1691
|
+
y: cursorPoint.y + (focusCandidate.y - cursorPoint.y) * strength
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const applyBloomCameraParallax = (anchor) => {
|
|
1696
|
+
const cursorPoint = cursorWorldPoint()
|
|
1697
|
+
if (!cursorPoint) {
|
|
1698
|
+
return anchor
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
const strength = bloomCameraParallaxStrength(state.transform.scale)
|
|
1702
|
+
return {
|
|
1703
|
+
x: anchor.x + (cursorPoint.x - anchor.x) * strength,
|
|
1704
|
+
y: anchor.y + (cursorPoint.y - anchor.y) * strength
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1617
1708
|
const visibilityScaleBucket = (scale) => {
|
|
1618
1709
|
const safeScale = Math.max(zoomRange.min, scale)
|
|
1619
1710
|
return Math.round(safeScale * 180_000)
|
|
@@ -3208,7 +3299,7 @@ const refreshRenderNodeDepthProjection = () => {
|
|
|
3208
3299
|
return
|
|
3209
3300
|
}
|
|
3210
3301
|
|
|
3211
|
-
const anchor = nodeProjectionAnchor()
|
|
3302
|
+
const anchor = applyBloomCameraParallax(nodeProjectionAnchor())
|
|
3212
3303
|
let maxDistance = 1
|
|
3213
3304
|
for (let index = 0; index < state.renderNodes.length; index += 1) {
|
|
3214
3305
|
const node = state.renderNodes[index]
|
|
@@ -3862,7 +3953,7 @@ const zoomAtPoint = (screenX, screenY, factor, source = 'generic') => {
|
|
|
3862
3953
|
state.zoomTransition.screenX === screenX &&
|
|
3863
3954
|
state.zoomTransition.screenY === screenY
|
|
3864
3955
|
? { x: state.zoomTransition.worldX, y: state.zoomTransition.worldY }
|
|
3865
|
-
:
|
|
3956
|
+
: resolveZoomAnchorWorldPoint(screenX, screenY, source)
|
|
3866
3957
|
const worldX = worldPointAtCursor.x
|
|
3867
3958
|
const worldY = worldPointAtCursor.y
|
|
3868
3959
|
state.lastZoomFocus = {
|
package/package.json
CHANGED