@andespindola/brainlink 0.1.0-beta.58 → 0.1.0-beta.59
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.
|
@@ -19,6 +19,9 @@ const hoverHitTestIntervalMs = 64
|
|
|
19
19
|
const overviewClusterMaxCount = 1400
|
|
20
20
|
const zoomRecoveryGuardMs = 1500
|
|
21
21
|
const zoomCapTargetViewportShare = 0.72
|
|
22
|
+
const meshEdgeScaleThreshold = 0.09
|
|
23
|
+
const meshEdgeMinBudget = 140
|
|
24
|
+
const meshEdgeMaxBudget = 1400
|
|
22
25
|
const state = {
|
|
23
26
|
graph: { nodes: [], edges: [] },
|
|
24
27
|
nodes: [],
|
|
@@ -595,6 +598,122 @@ const collectVisibleEdgesForNodes = nodeIds => {
|
|
|
595
598
|
return collected
|
|
596
599
|
}
|
|
597
600
|
|
|
601
|
+
const edgePairKey = (source, target) =>
|
|
602
|
+
source < target ? source + '|' + target : target + '|' + source
|
|
603
|
+
|
|
604
|
+
const meshNeighborBuckets = (nodes, cellSize) => {
|
|
605
|
+
const buckets = new Map()
|
|
606
|
+
|
|
607
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
608
|
+
const node = nodes[index]
|
|
609
|
+
const cellX = Math.floor(node.x / cellSize)
|
|
610
|
+
const cellY = Math.floor(node.y / cellSize)
|
|
611
|
+
const key = cellX + ':' + cellY
|
|
612
|
+
const bucket = buckets.get(key)
|
|
613
|
+
if (bucket) {
|
|
614
|
+
bucket.push(node)
|
|
615
|
+
} else {
|
|
616
|
+
buckets.set(key, [node])
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return buckets
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const meshCandidatesForNode = (node, buckets, cellSize) => {
|
|
624
|
+
const cellX = Math.floor(node.x / cellSize)
|
|
625
|
+
const cellY = Math.floor(node.y / cellSize)
|
|
626
|
+
const candidates = []
|
|
627
|
+
|
|
628
|
+
for (let offsetX = -1; offsetX <= 1; offsetX += 1) {
|
|
629
|
+
for (let offsetY = -1; offsetY <= 1; offsetY += 1) {
|
|
630
|
+
const bucket = buckets.get((cellX + offsetX) + ':' + (cellY + offsetY))
|
|
631
|
+
if (!bucket) continue
|
|
632
|
+
for (let index = 0; index < bucket.length; index += 1) {
|
|
633
|
+
const candidate = bucket[index]
|
|
634
|
+
if (candidate.id !== node.id) {
|
|
635
|
+
candidates.push(candidate)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return candidates
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const buildMeshEdgesForNodes = (nodes, existingEdges) => {
|
|
645
|
+
if (nodes.length < 2 || state.transform.scale < meshEdgeScaleThreshold) {
|
|
646
|
+
return []
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const existingKeys = new Set()
|
|
650
|
+
for (let index = 0; index < existingEdges.length; index += 1) {
|
|
651
|
+
const edge = existingEdges[index]
|
|
652
|
+
if (edge.target) {
|
|
653
|
+
existingKeys.add(edgePairKey(edge.source, edge.target))
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const desiredBudget = Math.min(
|
|
658
|
+
meshEdgeMaxBudget,
|
|
659
|
+
Math.max(meshEdgeMinBudget, Math.floor(edgeBudgetForCurrentFrame() * 0.62))
|
|
660
|
+
)
|
|
661
|
+
const perNodeNeighborCount =
|
|
662
|
+
state.transform.scale >= 1.05 ? 4
|
|
663
|
+
: state.transform.scale >= 0.62 ? 3
|
|
664
|
+
: 2
|
|
665
|
+
const cellSize = Math.max(120, 280 / Math.max(state.transform.scale, 0.0001))
|
|
666
|
+
const maxDistance = 980
|
|
667
|
+
const maxDistanceSquared = maxDistance * maxDistance
|
|
668
|
+
const buckets = meshNeighborBuckets(nodes, cellSize)
|
|
669
|
+
const meshEdges = []
|
|
670
|
+
const meshKeys = new Set()
|
|
671
|
+
|
|
672
|
+
for (let index = 0; index < nodes.length && meshEdges.length < desiredBudget; index += 1) {
|
|
673
|
+
const node = nodes[index]
|
|
674
|
+
const candidates = meshCandidatesForNode(node, buckets, cellSize)
|
|
675
|
+
.map((candidate) => ({
|
|
676
|
+
node: candidate,
|
|
677
|
+
distanceSquared: (candidate.x - node.x) ** 2 + (candidate.y - node.y) ** 2
|
|
678
|
+
}))
|
|
679
|
+
.filter((candidate) => candidate.distanceSquared <= maxDistanceSquared)
|
|
680
|
+
.sort((left, right) => left.distanceSquared - right.distanceSquared)
|
|
681
|
+
|
|
682
|
+
let linked = 0
|
|
683
|
+
for (let candidateIndex = 0; candidateIndex < candidates.length && linked < perNodeNeighborCount && meshEdges.length < desiredBudget; candidateIndex += 1) {
|
|
684
|
+
const candidate = candidates[candidateIndex].node
|
|
685
|
+
const key = edgePairKey(node.id, candidate.id)
|
|
686
|
+
if (existingKeys.has(key) || meshKeys.has(key)) {
|
|
687
|
+
continue
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
meshKeys.add(key)
|
|
691
|
+
meshEdges.push({
|
|
692
|
+
source: node.id,
|
|
693
|
+
target: candidate.id,
|
|
694
|
+
targetTitle: candidate.title,
|
|
695
|
+
weight: 1,
|
|
696
|
+
priority: 'normal',
|
|
697
|
+
sourceNode: node,
|
|
698
|
+
targetNode: candidate,
|
|
699
|
+
inferred: true
|
|
700
|
+
})
|
|
701
|
+
linked += 1
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return meshEdges
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const withMeshEdges = (nodes, edges) => {
|
|
709
|
+
if (nodes.length === 0 || state.visibleNodes.length <= largeGraphNodeThreshold || state.transform.scale < meshEdgeScaleThreshold) {
|
|
710
|
+
return edges
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const meshEdges = buildMeshEdgesForNodes(nodes, edges)
|
|
714
|
+
return meshEdges.length > 0 ? edges.concat(meshEdges) : edges
|
|
715
|
+
}
|
|
716
|
+
|
|
598
717
|
const fallbackViewportNodes = () => {
|
|
599
718
|
const nodes = []
|
|
600
719
|
const maxNodes = Math.min(renderNodeBudget, 220)
|
|
@@ -1232,7 +1351,7 @@ const computeRenderVisibility = () => {
|
|
|
1232
1351
|
state.renderNodes = state.visibleNodes
|
|
1233
1352
|
state.renderClusters = []
|
|
1234
1353
|
const ids = new Set(state.renderNodes.map((node) => node.id))
|
|
1235
|
-
state.renderEdges = collectVisibleEdgesForNodes(ids)
|
|
1354
|
+
state.renderEdges = withMeshEdges(state.renderNodes, collectVisibleEdgesForNodes(ids))
|
|
1236
1355
|
return
|
|
1237
1356
|
}
|
|
1238
1357
|
|
|
@@ -1256,7 +1375,7 @@ const computeRenderVisibility = () => {
|
|
|
1256
1375
|
|
|
1257
1376
|
state.renderClusters = []
|
|
1258
1377
|
state.renderNodes = sampledNodes
|
|
1259
|
-
state.renderEdges = sampledEdges
|
|
1378
|
+
state.renderEdges = withMeshEdges(sampledNodes, sampledEdges)
|
|
1260
1379
|
return
|
|
1261
1380
|
}
|
|
1262
1381
|
|
|
@@ -1265,7 +1384,7 @@ const computeRenderVisibility = () => {
|
|
|
1265
1384
|
const sampledIds = new Set(sampled.map((node) => node.id))
|
|
1266
1385
|
state.renderClusters = []
|
|
1267
1386
|
state.renderNodes = sampled
|
|
1268
|
-
state.renderEdges = collectVisibleEdgesForNodes(sampledIds)
|
|
1387
|
+
state.renderEdges = withMeshEdges(sampled, collectVisibleEdgesForNodes(sampledIds))
|
|
1269
1388
|
return
|
|
1270
1389
|
}
|
|
1271
1390
|
|
|
@@ -1301,7 +1420,7 @@ const computeRenderVisibility = () => {
|
|
|
1301
1420
|
const fallbackIds = new Set(fallbackNodes.map((node) => node.id))
|
|
1302
1421
|
state.renderNodes = fallbackNodes
|
|
1303
1422
|
state.renderClusters = []
|
|
1304
|
-
state.renderEdges = collectVisibleEdgesForNodes(fallbackIds)
|
|
1423
|
+
state.renderEdges = withMeshEdges(fallbackNodes, collectVisibleEdgesForNodes(fallbackIds))
|
|
1305
1424
|
return
|
|
1306
1425
|
}
|
|
1307
1426
|
|
|
@@ -1310,14 +1429,14 @@ const computeRenderVisibility = () => {
|
|
|
1310
1429
|
const edges = collectVisibleEdgesForNodes(nodeIds)
|
|
1311
1430
|
|
|
1312
1431
|
state.renderNodes = normalizedNodes
|
|
1313
|
-
state.renderEdges = edges
|
|
1432
|
+
state.renderEdges = withMeshEdges(normalizedNodes, edges)
|
|
1314
1433
|
|
|
1315
1434
|
if (state.renderNodes.length === 0 && state.visibleNodes.length > 0) {
|
|
1316
1435
|
const fallbackNodes = sampleVisibleNodes(Math.min(renderNodeBudget, 260))
|
|
1317
1436
|
const fallbackIds = new Set(fallbackNodes.map((node) => node.id))
|
|
1318
1437
|
state.renderClusters = []
|
|
1319
1438
|
state.renderNodes = fallbackNodes
|
|
1320
|
-
state.renderEdges = collectVisibleEdgesForNodes(fallbackIds)
|
|
1439
|
+
state.renderEdges = withMeshEdges(fallbackNodes, collectVisibleEdgesForNodes(fallbackIds))
|
|
1321
1440
|
}
|
|
1322
1441
|
}
|
|
1323
1442
|
|
|
@@ -1427,11 +1546,18 @@ const render = now => {
|
|
|
1427
1546
|
if (drawEdges) {
|
|
1428
1547
|
state.renderEdges.forEach(edge => {
|
|
1429
1548
|
const selectedEdge = state.selected && (edge.source === state.selected.id || edge.target === state.selected.id)
|
|
1549
|
+
const inferredEdge = Boolean(edge.inferred)
|
|
1430
1550
|
ctx.beginPath()
|
|
1431
1551
|
ctx.moveTo(edge.sourceNode.x, edge.sourceNode.y)
|
|
1432
1552
|
ctx.lineTo(edge.targetNode.x, edge.targetNode.y)
|
|
1433
|
-
ctx.strokeStyle = selectedEdge
|
|
1434
|
-
|
|
1553
|
+
ctx.strokeStyle = selectedEdge
|
|
1554
|
+
? graphTheme.edgeActive
|
|
1555
|
+
: inferredEdge
|
|
1556
|
+
? 'rgba(203, 213, 225, 0.1)'
|
|
1557
|
+
: graphTheme.edge
|
|
1558
|
+
ctx.lineWidth = inferredEdge
|
|
1559
|
+
? 0.82
|
|
1560
|
+
: (selectedEdge ? 1.8 : 1) + Math.min(edgeWeight(edge) - 1, 8) * 0.22
|
|
1435
1561
|
ctx.stroke()
|
|
1436
1562
|
})
|
|
1437
1563
|
}
|
package/package.json
CHANGED