@andespindola/brainlink 0.1.0-beta.136 → 0.1.0-beta.138
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.
|
@@ -89,6 +89,7 @@ const state = {
|
|
|
89
89
|
lastHoverHitAt: 0,
|
|
90
90
|
lastManualZoomAt: 0,
|
|
91
91
|
lastZoomFocus: { x: 0, y: 0, at: 0 },
|
|
92
|
+
leafFocusRootNodeId: null,
|
|
92
93
|
hierarchyFocusGroupId: null,
|
|
93
94
|
hierarchyFocusStack: [],
|
|
94
95
|
hierarchyRevealFocusGroupId: null,
|
|
@@ -814,14 +815,16 @@ const edgeRelevanceScore = edge => {
|
|
|
814
815
|
return score
|
|
815
816
|
}
|
|
816
817
|
|
|
817
|
-
const collectVisibleEdgesForNodes = nodeIds => {
|
|
818
|
+
const collectVisibleEdgesForNodes = (nodeIds, options = { preferComplete: false }) => {
|
|
818
819
|
if (nodeIds.size === 0) {
|
|
819
820
|
return []
|
|
820
821
|
}
|
|
821
822
|
|
|
822
823
|
const seen = new Set()
|
|
823
824
|
const candidates = []
|
|
824
|
-
const limit =
|
|
825
|
+
const limit = options.preferComplete
|
|
826
|
+
? hierarchyAbsoluteEdgeSafetyCap
|
|
827
|
+
: edgeBudgetForCurrentFrame()
|
|
825
828
|
|
|
826
829
|
nodeIds.forEach(nodeId => {
|
|
827
830
|
const candidateEdges = state.visibleEdgeByNode.get(nodeId) ?? []
|
|
@@ -1337,6 +1340,76 @@ const arrangeChildGraphNodes = (nodes, group, origin = group) => {
|
|
|
1337
1340
|
return arranged
|
|
1338
1341
|
}
|
|
1339
1342
|
|
|
1343
|
+
const projectNodesIntoChildGraph = (nodes, focusRenderNode, group) => {
|
|
1344
|
+
if (nodes.length <= 1) {
|
|
1345
|
+
return nodes.map(node => ({ ...node, x: focusRenderNode.x, y: focusRenderNode.y }))
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
let minX = Number.POSITIVE_INFINITY
|
|
1349
|
+
let maxX = Number.NEGATIVE_INFINITY
|
|
1350
|
+
let minY = Number.POSITIVE_INFINITY
|
|
1351
|
+
let maxY = Number.NEGATIVE_INFINITY
|
|
1352
|
+
|
|
1353
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
1354
|
+
const node = nodes[index]
|
|
1355
|
+
minX = Math.min(minX, node.x)
|
|
1356
|
+
maxX = Math.max(maxX, node.x)
|
|
1357
|
+
minY = Math.min(minY, node.y)
|
|
1358
|
+
maxY = Math.max(maxY, node.y)
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
const centerX = (minX + maxX) / 2
|
|
1362
|
+
const centerY = (minY + maxY) / 2
|
|
1363
|
+
const maxDistanceFromCenter = nodes.reduce((maxDistance, node) => {
|
|
1364
|
+
const distance = Math.hypot(node.x - centerX, node.y - centerY)
|
|
1365
|
+
return Math.max(maxDistance, distance)
|
|
1366
|
+
}, 1)
|
|
1367
|
+
const subgraphFillByNodeCount = (nodeCount) => {
|
|
1368
|
+
if (nodeCount <= 24) return 0.62
|
|
1369
|
+
if (nodeCount <= 80) return 0.7
|
|
1370
|
+
if (nodeCount <= 200) return 0.78
|
|
1371
|
+
return 0.84
|
|
1372
|
+
}
|
|
1373
|
+
const targetRadius = Math.max(1, childGraphRenderRadius(group) * subgraphFillByNodeCount(nodes.length))
|
|
1374
|
+
const scale = targetRadius / Math.max(maxDistanceFromCenter, 1)
|
|
1375
|
+
|
|
1376
|
+
return nodes.map(node => ({
|
|
1377
|
+
...node,
|
|
1378
|
+
x: focusRenderNode.x + (node.x - centerX) * scale,
|
|
1379
|
+
y: focusRenderNode.y + (node.y - centerY) * scale
|
|
1380
|
+
}))
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const recursiveLeafSubgraphNodes = (rootNodeId, maxNodes = renderNodeBudget) => {
|
|
1384
|
+
const root = state.nodeById.get(rootNodeId)
|
|
1385
|
+
if (!root) {
|
|
1386
|
+
return []
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
const visited = new Set([rootNodeId])
|
|
1390
|
+
const queue = [rootNodeId]
|
|
1391
|
+
|
|
1392
|
+
while (queue.length > 0 && visited.size < maxNodes) {
|
|
1393
|
+
const currentId = queue.shift()
|
|
1394
|
+
const edges = [...(state.visibleEdgeByNode.get(currentId) ?? [])]
|
|
1395
|
+
.filter(edge => edge.target)
|
|
1396
|
+
.sort((left, right) => edgeWeight(right) - edgeWeight(left))
|
|
1397
|
+
for (let edgeIndex = 0; edgeIndex < edges.length && visited.size < maxNodes; edgeIndex += 1) {
|
|
1398
|
+
const edge = edges[edgeIndex]
|
|
1399
|
+
const nextId = edge.source === currentId ? edge.target : edge.source
|
|
1400
|
+
if (!nextId || visited.has(nextId) || !state.nodeById.has(nextId)) {
|
|
1401
|
+
continue
|
|
1402
|
+
}
|
|
1403
|
+
visited.add(nextId)
|
|
1404
|
+
queue.push(nextId)
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return [...visited]
|
|
1409
|
+
.map(id => state.nodeById.get(id))
|
|
1410
|
+
.filter(Boolean)
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1340
1413
|
const arrangeChildGroupNodes = (groups, parentGroup, origin) => {
|
|
1341
1414
|
if (groups.length <= 1) {
|
|
1342
1415
|
return groups.map(group => ({
|
|
@@ -1502,6 +1575,7 @@ const updateHierarchyFocusGroup = (groups, _viewport) => {
|
|
|
1502
1575
|
state.selected = null
|
|
1503
1576
|
}
|
|
1504
1577
|
}
|
|
1578
|
+
state.leafFocusRootNodeId = null
|
|
1505
1579
|
state.hierarchyFocusGroupId = null
|
|
1506
1580
|
return null
|
|
1507
1581
|
}
|
|
@@ -1622,12 +1696,23 @@ const computeHierarchyRenderVisibility = (viewport) => {
|
|
|
1622
1696
|
const focusChildGroups = focus.childGroupIds
|
|
1623
1697
|
.map(groupId => state.groupById.get(groupId))
|
|
1624
1698
|
.filter(Boolean)
|
|
1699
|
+
if (focusChildGroups.length > 0) {
|
|
1700
|
+
state.leafFocusRootNodeId = null
|
|
1701
|
+
} else if (state.leafFocusRootNodeId && !focusIds.has(state.leafFocusRootNodeId)) {
|
|
1702
|
+
state.leafFocusRootNodeId = null
|
|
1703
|
+
}
|
|
1625
1704
|
const childTargetLimit = Math.min(renderNodeBudget, Math.max(1, Math.floor(renderNodeBudget * revealProgress)))
|
|
1626
1705
|
const childLimit = updateHierarchyChildRevealBudget(focus.id, childTargetLimit)
|
|
1627
1706
|
const focusRenderNode = groupNodes.find(node => node.groupId === focus.id) ?? createGroupRenderNode(focus)
|
|
1628
|
-
const arrangedChildren =
|
|
1629
|
-
|
|
1630
|
-
|
|
1707
|
+
const arrangedChildren = (() => {
|
|
1708
|
+
if (focusChildGroups.length > 0) {
|
|
1709
|
+
return arrangeChildGroupNodes(focusChildGroups, focus, focusRenderNode)
|
|
1710
|
+
}
|
|
1711
|
+
const recursiveNodes = state.leafFocusRootNodeId
|
|
1712
|
+
? recursiveLeafSubgraphNodes(state.leafFocusRootNodeId, renderNodeBudget)
|
|
1713
|
+
: rawChildNodes
|
|
1714
|
+
return projectNodesIntoChildGraph(recursiveNodes, focusRenderNode, focus)
|
|
1715
|
+
})()
|
|
1631
1716
|
const childNodes = selectStableSampleNodes(arrangedChildren, childLimit)
|
|
1632
1717
|
.map(node => interpolateNodeFromGroup(node, focusRenderNode, revealProgress))
|
|
1633
1718
|
const childIds = new Set(childNodes.map(node => node.id))
|
|
@@ -1640,7 +1725,7 @@ const computeHierarchyRenderVisibility = (viewport) => {
|
|
|
1640
1725
|
const childEdges = (isMicroView || revealProgress > 0)
|
|
1641
1726
|
? focusChildGroups.length > 0
|
|
1642
1727
|
? groupEdgesForRenderedGroups(childNodes)
|
|
1643
|
-
: collectVisibleEdgesForNodes(childIds).map(edge => ({
|
|
1728
|
+
: collectVisibleEdgesForNodes(childIds, { preferComplete: true }).map(edge => ({
|
|
1644
1729
|
...edge,
|
|
1645
1730
|
sourceNode: childById.get(edge.source) ?? edge.sourceNode,
|
|
1646
1731
|
targetNode: childById.get(edge.target) ?? edge.targetNode
|
|
@@ -2121,6 +2206,7 @@ const fitView = (options = { useFiltered: true, preferHubCenter: true }) => {
|
|
|
2121
2206
|
const resetView = () => fitView({ useFiltered: false, preferHubCenter: false })
|
|
2122
2207
|
|
|
2123
2208
|
const resetHierarchyFocus = () => {
|
|
2209
|
+
state.leafFocusRootNodeId = null
|
|
2124
2210
|
state.hierarchyFocusGroupId = null
|
|
2125
2211
|
state.hierarchyFocusStack = []
|
|
2126
2212
|
state.hierarchyRevealFocusGroupId = null
|
|
@@ -3128,6 +3214,22 @@ const expandGroupNode = node => {
|
|
|
3128
3214
|
}
|
|
3129
3215
|
}
|
|
3130
3216
|
|
|
3217
|
+
const expandLeafNodeGraph = node => {
|
|
3218
|
+
if (!node || node.isGroupNode) return
|
|
3219
|
+
state.selected = node
|
|
3220
|
+
state.leafFocusRootNodeId = node.id
|
|
3221
|
+
state.zoomTransition = {
|
|
3222
|
+
active: true,
|
|
3223
|
+
source: 'group',
|
|
3224
|
+
screenX: state.viewport.width / 2,
|
|
3225
|
+
screenY: state.viewport.height / 2,
|
|
3226
|
+
worldX: node.x,
|
|
3227
|
+
worldY: node.y,
|
|
3228
|
+
targetScale: clampScale(Math.max(state.transform.scale * 1.08, hierarchyMicroExitScale * 1.02))
|
|
3229
|
+
}
|
|
3230
|
+
state.lastZoomFocus = { x: node.x, y: node.y, at: performance.now() }
|
|
3231
|
+
markRenderDirty()
|
|
3232
|
+
}
|
|
3131
3233
|
const handleGroupNodePrimaryClick = node => {
|
|
3132
3234
|
if (!node?.isGroupNode) return
|
|
3133
3235
|
const alreadyFocused = state.selected?.isGroupNode && state.selected.groupId === node.groupId
|
|
@@ -3138,8 +3240,19 @@ const handleGroupNodePrimaryClick = node => {
|
|
|
3138
3240
|
expandGroupNode(node)
|
|
3139
3241
|
}
|
|
3140
3242
|
|
|
3243
|
+
const handleLeafNodePrimaryClick = node => {
|
|
3244
|
+
if (!node || node.isGroupNode) return
|
|
3245
|
+
const sameFocusedNode = state.leafFocusRootNodeId === node.id
|
|
3246
|
+
if (!sameFocusedNode) {
|
|
3247
|
+
expandLeafNodeGraph(node)
|
|
3248
|
+
return
|
|
3249
|
+
}
|
|
3250
|
+
selectNode(node, { openContent: true })
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3141
3253
|
const selectNode = (node, options = { openContent: false }) => {
|
|
3142
3254
|
if (!node) {
|
|
3255
|
+
state.leafFocusRootNodeId = null
|
|
3143
3256
|
state.selected = null
|
|
3144
3257
|
if (elements.contentDialog.open) {
|
|
3145
3258
|
elements.contentDialog.close()
|
|
@@ -3376,6 +3489,8 @@ const bindEvents = () => {
|
|
|
3376
3489
|
const clickedNode = draggedNode ?? state.hovered
|
|
3377
3490
|
if (clickedNode?.isGroupNode) {
|
|
3378
3491
|
handleGroupNodePrimaryClick(clickedNode)
|
|
3492
|
+
} else if (clickedNode && state.hierarchyFocusGroupId) {
|
|
3493
|
+
handleLeafNodePrimaryClick(clickedNode)
|
|
3379
3494
|
} else {
|
|
3380
3495
|
selectNode(clickedNode, { openContent: true })
|
|
3381
3496
|
}
|
|
@@ -3465,6 +3580,7 @@ const loadGraph = async (options = { reset: false }) => {
|
|
|
3465
3580
|
state.graph = graph
|
|
3466
3581
|
state.nodes = layout.nodes
|
|
3467
3582
|
state.groups = layout.groups
|
|
3583
|
+
state.leafFocusRootNodeId = null
|
|
3468
3584
|
state.hierarchyFocusGroupId = null
|
|
3469
3585
|
state.hierarchyFocusStack = []
|
|
3470
3586
|
state.hierarchyRevealFocusGroupId = null
|
package/package.json
CHANGED