@andespindola/brainlink 0.1.0-beta.61 → 0.1.0-beta.63
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.
|
@@ -38,7 +38,7 @@ export const createClientHtml = () => `<!doctype html>
|
|
|
38
38
|
<div class="toolbar" aria-label="Graph controls">
|
|
39
39
|
<button id="zoomIn" type="button" title="Zoom in">+</button>
|
|
40
40
|
<button id="zoomOut" type="button" title="Zoom out">-</button>
|
|
41
|
-
<button id="fit" type="button" title="
|
|
41
|
+
<button id="fit" type="button" title="Focus central hub">◎</button>
|
|
42
42
|
<button id="reset" type="button" title="Reset view">⌂</button>
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
@@ -23,6 +23,8 @@ const meshEdgeScaleThreshold = 0.09
|
|
|
23
23
|
const meshEdgeMinBudget = 140
|
|
24
24
|
const meshEdgeMaxBudget = 1400
|
|
25
25
|
const layeredCoreScaleThreshold = 0.55
|
|
26
|
+
const dragNeighborhoodMaxAffected = 180
|
|
27
|
+
const dragSettleRounds = 3
|
|
26
28
|
const state = {
|
|
27
29
|
graph: { nodes: [], edges: [] },
|
|
28
30
|
nodes: [],
|
|
@@ -569,13 +571,12 @@ const nodeBudgetForScale = (scale) => {
|
|
|
569
571
|
}
|
|
570
572
|
|
|
571
573
|
const layerWindowForScale = (scale) => {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
return { inner: 0, outer: 0.14 }
|
|
574
|
+
const normalized = Math.max(0, Math.min(1, (scale - 0.06) / 0.94))
|
|
575
|
+
const outer = Math.max(0.14, 1 - normalized * 0.86)
|
|
576
|
+
const band = Math.max(0.14, 0.26 - normalized * 0.12)
|
|
577
|
+
const inner = Math.max(0, outer - band)
|
|
578
|
+
|
|
579
|
+
return { inner, outer }
|
|
579
580
|
}
|
|
580
581
|
|
|
581
582
|
const selectLayeredNodesForScale = (sourceNodes) => {
|
|
@@ -1141,6 +1142,27 @@ const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: t
|
|
|
1141
1142
|
|
|
1142
1143
|
const resetView = () => fitView({ useFiltered: false, macro: true, preferHubCenter: true })
|
|
1143
1144
|
|
|
1145
|
+
const focusPrimaryHub = () => {
|
|
1146
|
+
const hub = state.primaryHub
|
|
1147
|
+
if (!hub) {
|
|
1148
|
+
fitView({ useFiltered: true, macro: false, preferHubCenter: true })
|
|
1149
|
+
return
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const rect = canvas.getBoundingClientRect()
|
|
1153
|
+
const width = Math.max(rect.width, 320)
|
|
1154
|
+
const height = Math.max(rect.height, 320)
|
|
1155
|
+
const targetScale = clampScale(Math.max(0.78, state.transform.scale))
|
|
1156
|
+
|
|
1157
|
+
state.transform = {
|
|
1158
|
+
x: clampTransformCoordinate(width / 2 - hub.x * targetScale),
|
|
1159
|
+
y: clampTransformCoordinate(height / 2 - hub.y * targetScale),
|
|
1160
|
+
scale: targetScale
|
|
1161
|
+
}
|
|
1162
|
+
state.offscreenFrameCount = 0
|
|
1163
|
+
markRenderDirty()
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1144
1166
|
const createLayout = graph => {
|
|
1145
1167
|
const nodeRows = Array.isArray(graph.nodes) ? graph.nodes : []
|
|
1146
1168
|
const edgeRows = Array.isArray(graph.edges) ? graph.edges : []
|
|
@@ -1354,6 +1376,104 @@ const worldPoint = event => {
|
|
|
1354
1376
|
}
|
|
1355
1377
|
}
|
|
1356
1378
|
|
|
1379
|
+
const connectedNodeIdsFor = (nodeId) => {
|
|
1380
|
+
const edges = state.visibleEdgeByNode.get(nodeId) ?? []
|
|
1381
|
+
const ids = new Set()
|
|
1382
|
+
|
|
1383
|
+
for (let index = 0; index < edges.length; index += 1) {
|
|
1384
|
+
const edge = edges[index]
|
|
1385
|
+
if (!edge.target) continue
|
|
1386
|
+
if (edge.source === nodeId) {
|
|
1387
|
+
ids.add(edge.target)
|
|
1388
|
+
} else if (edge.target === nodeId) {
|
|
1389
|
+
ids.add(edge.source)
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
return ids
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
const applyDragNeighborhoodAdjustment = (dragNode, deltaX, deltaY) => {
|
|
1397
|
+
if (!dragNode) return
|
|
1398
|
+
if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY)) return
|
|
1399
|
+
if (Math.abs(deltaX) + Math.abs(deltaY) <= 0.001) return
|
|
1400
|
+
|
|
1401
|
+
const scale = Math.max(state.transform.scale, 0.0001)
|
|
1402
|
+
const influenceRadius = Math.max(220, Math.min(920, 440 / scale))
|
|
1403
|
+
const influenceRadiusSquared = influenceRadius * influenceRadius
|
|
1404
|
+
const connectedIds = connectedNodeIdsFor(dragNode.id)
|
|
1405
|
+
const candidates = state.renderNodes.length > 0 ? state.renderNodes : state.visibleNodes
|
|
1406
|
+
let adjusted = 0
|
|
1407
|
+
|
|
1408
|
+
for (let index = 0; index < candidates.length && adjusted < dragNeighborhoodMaxAffected; index += 1) {
|
|
1409
|
+
const node = candidates[index]
|
|
1410
|
+
if (node.id === dragNode.id) continue
|
|
1411
|
+
|
|
1412
|
+
const isConnected = connectedIds.has(node.id)
|
|
1413
|
+
const dx = node.x - dragNode.x
|
|
1414
|
+
const dy = node.y - dragNode.y
|
|
1415
|
+
const distanceSquared = dx * dx + dy * dy
|
|
1416
|
+
const withinRadius = distanceSquared <= influenceRadiusSquared
|
|
1417
|
+
if (!isConnected && !withinRadius) continue
|
|
1418
|
+
|
|
1419
|
+
const distance = Math.max(Math.sqrt(distanceSquared), 0.0001)
|
|
1420
|
+
const proximity = withinRadius ? 1 - (distance / influenceRadius) : 0
|
|
1421
|
+
const coupledStrength = isConnected ? 0.28 : 0.12
|
|
1422
|
+
const influence = Math.min(0.46, coupledStrength + proximity * 0.34)
|
|
1423
|
+
node.x += deltaX * influence
|
|
1424
|
+
node.y += deltaY * influence
|
|
1425
|
+
node.vx = (Number.isFinite(node.vx) ? node.vx : 0) + deltaX * influence * 0.06
|
|
1426
|
+
node.vy = (Number.isFinite(node.vy) ? node.vy : 0) + deltaY * influence * 0.06
|
|
1427
|
+
adjusted += 1
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
const settleNeighborhoodAroundNode = (dragNode) => {
|
|
1432
|
+
if (!dragNode) return
|
|
1433
|
+
|
|
1434
|
+
const scale = Math.max(state.transform.scale, 0.0001)
|
|
1435
|
+
const settleRadius = Math.max(240, Math.min(980, 520 / scale))
|
|
1436
|
+
const settleRadiusSquared = settleRadius * settleRadius
|
|
1437
|
+
const connectedIds = connectedNodeIdsFor(dragNode.id)
|
|
1438
|
+
const candidates = (state.renderNodes.length > 0 ? state.renderNodes : state.visibleNodes)
|
|
1439
|
+
.filter((node) => {
|
|
1440
|
+
if (node.id === dragNode.id) return true
|
|
1441
|
+
const dx = node.x - dragNode.x
|
|
1442
|
+
const dy = node.y - dragNode.y
|
|
1443
|
+
const distanceSquared = dx * dx + dy * dy
|
|
1444
|
+
return connectedIds.has(node.id) || distanceSquared <= settleRadiusSquared
|
|
1445
|
+
})
|
|
1446
|
+
.slice(0, dragNeighborhoodMaxAffected)
|
|
1447
|
+
|
|
1448
|
+
if (candidates.length <= 1) return
|
|
1449
|
+
|
|
1450
|
+
for (let round = 0; round < dragSettleRounds; round += 1) {
|
|
1451
|
+
for (let leftIndex = 0; leftIndex < candidates.length; leftIndex += 1) {
|
|
1452
|
+
const left = candidates[leftIndex]
|
|
1453
|
+
for (let rightIndex = leftIndex + 1; rightIndex < candidates.length; rightIndex += 1) {
|
|
1454
|
+
const right = candidates[rightIndex]
|
|
1455
|
+
const dx = right.x - left.x
|
|
1456
|
+
const dy = right.y - left.y
|
|
1457
|
+
const distance = Math.max(Math.hypot(dx, dy), 0.001)
|
|
1458
|
+
const minDistance = baseNodeRadius(left) + baseNodeRadius(right) + 10
|
|
1459
|
+
if (distance >= minDistance) continue
|
|
1460
|
+
|
|
1461
|
+
const push = (minDistance - distance) * 0.36
|
|
1462
|
+
const ux = dx / distance
|
|
1463
|
+
const uy = dy / distance
|
|
1464
|
+
if (left.id !== dragNode.id) {
|
|
1465
|
+
left.x -= ux * push
|
|
1466
|
+
left.y -= uy * push
|
|
1467
|
+
}
|
|
1468
|
+
if (right.id !== dragNode.id) {
|
|
1469
|
+
right.x += ux * push
|
|
1470
|
+
right.y += uy * push
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1357
1477
|
const hitNode = point => {
|
|
1358
1478
|
computeRenderVisibility()
|
|
1359
1479
|
if (state.renderClusters.length > 0) {
|
|
@@ -1920,8 +2040,8 @@ const wheelZoomFactor = event => {
|
|
|
1920
2040
|
return 1
|
|
1921
2041
|
}
|
|
1922
2042
|
|
|
1923
|
-
const baseStep = Math.max(0.
|
|
1924
|
-
const adjustedStep = baseStep * (isModifierZoom ? 1.
|
|
2043
|
+
const baseStep = Math.max(0.03, Math.min(0.2, absoluteDelta / 680))
|
|
2044
|
+
const adjustedStep = baseStep * (isModifierZoom ? 1.24 : 1)
|
|
1925
2045
|
|
|
1926
2046
|
return event.deltaY < 0 ? 1 + adjustedStep : 1 / (1 + adjustedStep)
|
|
1927
2047
|
}
|
|
@@ -1968,15 +2088,15 @@ const bindEvents = () => {
|
|
|
1968
2088
|
})
|
|
1969
2089
|
elements.zoomIn.addEventListener('click', () => {
|
|
1970
2090
|
const rect = canvas.getBoundingClientRect()
|
|
1971
|
-
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 1.
|
|
2091
|
+
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 1.14)
|
|
1972
2092
|
})
|
|
1973
2093
|
elements.zoomOut.addEventListener('click', () => {
|
|
1974
2094
|
const rect = canvas.getBoundingClientRect()
|
|
1975
|
-
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 0.
|
|
2095
|
+
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 0.88)
|
|
1976
2096
|
})
|
|
1977
2097
|
if (elements.fit) {
|
|
1978
2098
|
elements.fit.addEventListener('click', () => {
|
|
1979
|
-
|
|
2099
|
+
focusPrimaryHub()
|
|
1980
2100
|
})
|
|
1981
2101
|
}
|
|
1982
2102
|
elements.reset.addEventListener('click', () => {
|
|
@@ -1996,7 +2116,7 @@ const bindEvents = () => {
|
|
|
1996
2116
|
const rect = canvas.getBoundingClientRect()
|
|
1997
2117
|
const cursorX = event.clientX - rect.left
|
|
1998
2118
|
const cursorY = event.clientY - rect.top
|
|
1999
|
-
zoomAtPoint(cursorX, cursorY, 1.
|
|
2119
|
+
zoomAtPoint(cursorX, cursorY, 1.12)
|
|
2000
2120
|
})
|
|
2001
2121
|
canvas.addEventListener('pointerdown', event => {
|
|
2002
2122
|
const point = worldPoint(event)
|
|
@@ -2030,8 +2150,12 @@ const bindEvents = () => {
|
|
|
2030
2150
|
state.pointer.y = event.clientY
|
|
2031
2151
|
state.pointer.moved = state.pointer.moved || Math.abs(dx) + Math.abs(dy) > 3
|
|
2032
2152
|
if (state.pointer.dragNode) {
|
|
2033
|
-
state.pointer.dragNode
|
|
2034
|
-
|
|
2153
|
+
const dragNode = state.pointer.dragNode
|
|
2154
|
+
const previousX = dragNode.x
|
|
2155
|
+
const previousY = dragNode.y
|
|
2156
|
+
dragNode.x = point.x
|
|
2157
|
+
dragNode.y = point.y
|
|
2158
|
+
applyDragNeighborhoodAdjustment(dragNode, dragNode.x - previousX, dragNode.y - previousY)
|
|
2035
2159
|
markRenderDirty()
|
|
2036
2160
|
return
|
|
2037
2161
|
}
|
|
@@ -2043,8 +2167,13 @@ const bindEvents = () => {
|
|
|
2043
2167
|
markRenderDirty()
|
|
2044
2168
|
})
|
|
2045
2169
|
canvas.addEventListener('pointerup', event => {
|
|
2046
|
-
|
|
2047
|
-
if (
|
|
2170
|
+
const draggedNode = state.pointer.dragNode
|
|
2171
|
+
if (draggedNode && state.pointer.moved) {
|
|
2172
|
+
settleNeighborhoodAroundNode(draggedNode)
|
|
2173
|
+
markRenderDirty()
|
|
2174
|
+
}
|
|
2175
|
+
if (draggedNode && !state.pointer.moved) selectNode(draggedNode, { openContent: true })
|
|
2176
|
+
if (!draggedNode && !state.pointer.moved) selectNode(state.hovered, { openContent: true })
|
|
2048
2177
|
state.pointer = { x: 0, y: 0, down: false, dragNode: null, moved: false }
|
|
2049
2178
|
canvas.releasePointerCapture(event.pointerId)
|
|
2050
2179
|
})
|
|
@@ -2061,14 +2190,14 @@ const bindEvents = () => {
|
|
|
2061
2190
|
if (event.key === '+' || event.key === '=') {
|
|
2062
2191
|
event.preventDefault()
|
|
2063
2192
|
const rect = canvas.getBoundingClientRect()
|
|
2064
|
-
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 1.
|
|
2193
|
+
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 1.12)
|
|
2065
2194
|
return
|
|
2066
2195
|
}
|
|
2067
2196
|
|
|
2068
2197
|
if (event.key === '-' || event.key === '_') {
|
|
2069
2198
|
event.preventDefault()
|
|
2070
2199
|
const rect = canvas.getBoundingClientRect()
|
|
2071
|
-
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 0.
|
|
2200
|
+
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 0.89)
|
|
2072
2201
|
return
|
|
2073
2202
|
}
|
|
2074
2203
|
|
package/package.json
CHANGED