@andespindola/brainlink 0.1.0-beta.116 → 0.1.0-beta.117
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.
|
@@ -6,7 +6,8 @@ const massiveGraphNodeThreshold = 20000
|
|
|
6
6
|
const largeGraphEdgeRenderLimit = 120000
|
|
7
7
|
const renderNodeBudget = 900
|
|
8
8
|
const zoomedMassiveRenderNodeBudget = 2200
|
|
9
|
-
const
|
|
9
|
+
const massiveOverviewRenderNodeBudget = 1800
|
|
10
|
+
const massiveOverviewScaleThreshold = 0.065
|
|
10
11
|
const massiveAutoFitMacroScale = 0.018
|
|
11
12
|
const minNodePixelRadius = 2.3
|
|
12
13
|
const viewportPaddingPx = 280
|
|
@@ -17,7 +18,6 @@ const zoomRecoveryGuardMs = 4200
|
|
|
17
18
|
const meshEdgeScaleThreshold = 0.09
|
|
18
19
|
const meshEdgeMinBudget = 140
|
|
19
20
|
const meshEdgeMaxBudget = 1400
|
|
20
|
-
const layeredCoreScaleThreshold = 0.55
|
|
21
21
|
const dragNeighborhoodMaxAffected = 180
|
|
22
22
|
const dragSettleRounds = 3
|
|
23
23
|
const wheelZoomExponent = 0.0009
|
|
@@ -42,7 +42,6 @@ const state = {
|
|
|
42
42
|
visibleEdges: [],
|
|
43
43
|
renderNodes: [],
|
|
44
44
|
renderEdges: [],
|
|
45
|
-
renderNodeDepthProjectionById: new Map(),
|
|
46
45
|
nodeDegrees: new Map(),
|
|
47
46
|
selected: null,
|
|
48
47
|
hovered: null,
|
|
@@ -73,7 +72,6 @@ const state = {
|
|
|
73
72
|
lastHoverHitAt: 0,
|
|
74
73
|
lastManualZoomAt: 0,
|
|
75
74
|
lastZoomFocus: { x: 0, y: 0, at: 0 },
|
|
76
|
-
depthProjectionActive: false,
|
|
77
75
|
zoomTransition: {
|
|
78
76
|
active: false,
|
|
79
77
|
source: 'generic',
|
|
@@ -635,12 +633,6 @@ const createVisibleEdgeLookup = edges => {
|
|
|
635
633
|
return lookup
|
|
636
634
|
}
|
|
637
635
|
|
|
638
|
-
const isClusterInViewport = (cluster, viewport) =>
|
|
639
|
-
cluster.x >= viewport.minX &&
|
|
640
|
-
cluster.x <= viewport.maxX &&
|
|
641
|
-
cluster.y >= viewport.minY &&
|
|
642
|
-
cluster.y <= viewport.maxY
|
|
643
|
-
|
|
644
636
|
const edgeBudgetForCurrentFrame = () => {
|
|
645
637
|
const zoom = state.transform.scale
|
|
646
638
|
if (zoom < 0.12) return 380
|
|
@@ -652,112 +644,24 @@ const edgeBudgetForCurrentFrame = () => {
|
|
|
652
644
|
return 7600
|
|
653
645
|
}
|
|
654
646
|
|
|
655
|
-
const clusterBudgetForScale = (scale) => {
|
|
656
|
-
if (scale < 0.008) return 90
|
|
657
|
-
if (scale < 0.014) return 150
|
|
658
|
-
if (scale < 0.022) return 240
|
|
659
|
-
if (scale < 0.035) return 360
|
|
660
|
-
return 520
|
|
661
|
-
}
|
|
662
|
-
|
|
663
647
|
const nodeBudgetForScale = (scale) => {
|
|
664
|
-
if (scale < 0.035) return 220
|
|
665
|
-
if (scale < 0.06) return 360
|
|
666
|
-
if (scale < 0.09) return 520
|
|
667
|
-
if (scale < 0.14) return 720
|
|
668
648
|
if (state.visibleNodes.length > massiveGraphNodeThreshold) {
|
|
649
|
+
if (scale < massiveOverviewScaleThreshold) return massiveOverviewRenderNodeBudget
|
|
650
|
+
if (scale < 0.09) return 1600
|
|
651
|
+
if (scale < 0.14) return 1800
|
|
669
652
|
if (scale < 0.28) return renderNodeBudget
|
|
670
653
|
if (scale < 0.45) return 1100
|
|
671
654
|
if (scale < 0.7) return 1400
|
|
672
655
|
if (scale < 1.05) return 1800
|
|
673
656
|
return zoomedMassiveRenderNodeBudget
|
|
674
657
|
}
|
|
658
|
+
if (scale < 0.035) return 220
|
|
659
|
+
if (scale < 0.06) return 360
|
|
660
|
+
if (scale < 0.09) return 520
|
|
661
|
+
if (scale < 0.14) return 720
|
|
675
662
|
return renderNodeBudget
|
|
676
663
|
}
|
|
677
664
|
|
|
678
|
-
const layerFocusForScale = (scale) => {
|
|
679
|
-
const normalized = Math.max(0, Math.min(1, (scale - 0.06) / 0.94))
|
|
680
|
-
const shellCenter = Math.max(0.08, 0.96 - normalized * 0.86)
|
|
681
|
-
const shellWidth = Math.max(0.24, 0.46 - normalized * 0.16)
|
|
682
|
-
const coreRadius = Math.max(0.06, 0.1 + normalized * 0.22)
|
|
683
|
-
const coreRatio = Math.max(0.2, Math.min(0.72, 0.24 + normalized * 0.48))
|
|
684
|
-
|
|
685
|
-
return { shellCenter, shellWidth, coreRadius, coreRatio }
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const selectLayeredNodesForScale = (sourceNodes, targetCount) => {
|
|
689
|
-
const hub = state.primaryHub
|
|
690
|
-
if (!hub || sourceNodes.length <= 1200 || state.visibleNodes.length <= massiveGraphNodeThreshold) {
|
|
691
|
-
return sourceNodes
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
let maxDistance = 0
|
|
695
|
-
const distances = sourceNodes.map((node) => {
|
|
696
|
-
const distance = Math.hypot(node.x - hub.x, node.y - hub.y)
|
|
697
|
-
if (distance > maxDistance) {
|
|
698
|
-
maxDistance = distance
|
|
699
|
-
}
|
|
700
|
-
return { node, distance }
|
|
701
|
-
})
|
|
702
|
-
|
|
703
|
-
if (maxDistance <= 0.001) {
|
|
704
|
-
return sourceNodes
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const focus = layerFocusForScale(state.transform.scale)
|
|
708
|
-
const normalizedRows = distances.map((item) => ({
|
|
709
|
-
...item,
|
|
710
|
-
normalized: item.distance / maxDistance
|
|
711
|
-
}))
|
|
712
|
-
const desired = Math.max(260, Math.min(sourceNodes.length, targetCount * 2))
|
|
713
|
-
const coreTarget = Math.max(36, Math.min(desired - 8, Math.floor(desired * focus.coreRatio)))
|
|
714
|
-
const shellTarget = Math.max(12, desired - coreTarget)
|
|
715
|
-
const shellHalf = focus.shellWidth / 2
|
|
716
|
-
|
|
717
|
-
const coreNodes = normalizedRows
|
|
718
|
-
.filter((item) => item.normalized <= focus.coreRadius)
|
|
719
|
-
.sort((left, right) => {
|
|
720
|
-
const leftScore = state.nodeDegrees.get(left.node.id) ?? 0
|
|
721
|
-
const rightScore = state.nodeDegrees.get(right.node.id) ?? 0
|
|
722
|
-
if (leftScore !== rightScore) return rightScore - leftScore
|
|
723
|
-
return left.node.id.localeCompare(right.node.id)
|
|
724
|
-
})
|
|
725
|
-
.slice(0, coreTarget)
|
|
726
|
-
.map((item) => item.node)
|
|
727
|
-
|
|
728
|
-
const shellNodes = normalizedRows
|
|
729
|
-
.sort((left, right) => {
|
|
730
|
-
const leftDelta = Math.abs(left.normalized - focus.shellCenter)
|
|
731
|
-
const rightDelta = Math.abs(right.normalized - focus.shellCenter)
|
|
732
|
-
const leftInside = leftDelta <= shellHalf ? 0 : 1
|
|
733
|
-
const rightInside = rightDelta <= shellHalf ? 0 : 1
|
|
734
|
-
if (leftInside !== rightInside) return leftInside - rightInside
|
|
735
|
-
if (leftDelta !== rightDelta) return leftDelta - rightDelta
|
|
736
|
-
const leftScore = state.nodeDegrees.get(left.node.id) ?? 0
|
|
737
|
-
const rightScore = state.nodeDegrees.get(right.node.id) ?? 0
|
|
738
|
-
if (leftScore !== rightScore) return rightScore - leftScore
|
|
739
|
-
return left.node.id.localeCompare(right.node.id)
|
|
740
|
-
})
|
|
741
|
-
.slice(0, shellTarget)
|
|
742
|
-
.map((item) => item.node)
|
|
743
|
-
|
|
744
|
-
const merged = []
|
|
745
|
-
const ids = new Set()
|
|
746
|
-
const pushUnique = (node) => {
|
|
747
|
-
if (!node || ids.has(node.id)) return
|
|
748
|
-
ids.add(node.id)
|
|
749
|
-
merged.push(node)
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
if (state.transform.scale >= layeredCoreScaleThreshold) {
|
|
753
|
-
pushUnique(hub)
|
|
754
|
-
}
|
|
755
|
-
for (let index = 0; index < coreNodes.length; index += 1) pushUnique(coreNodes[index])
|
|
756
|
-
for (let index = 0; index < shellNodes.length; index += 1) pushUnique(shellNodes[index])
|
|
757
|
-
|
|
758
|
-
return merged.length > 0 ? merged : sourceNodes
|
|
759
|
-
}
|
|
760
|
-
|
|
761
665
|
const viewportCenterWorldPoint = () => {
|
|
762
666
|
const viewport = worldViewportBounds()
|
|
763
667
|
return {
|
|
@@ -857,31 +761,6 @@ const selectStableSampleNodes = (sourceNodes, limit) => {
|
|
|
857
761
|
.slice(0, limit)
|
|
858
762
|
}
|
|
859
763
|
|
|
860
|
-
const selectAccessBridgeNodes = (sourceNodes, limit) => {
|
|
861
|
-
if (limit <= 0 || sourceNodes.length === 0) {
|
|
862
|
-
return []
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
const now = performance.now()
|
|
866
|
-
const cursorPoint = cursorWorldPoint()
|
|
867
|
-
const recentZoomFocus =
|
|
868
|
-
now - state.lastZoomFocus.at <= 1200
|
|
869
|
-
? { x: state.lastZoomFocus.x, y: state.lastZoomFocus.y }
|
|
870
|
-
: null
|
|
871
|
-
const anchor = cursorPoint ?? recentZoomFocus ?? viewportCenterWorldPoint()
|
|
872
|
-
return [...sourceNodes]
|
|
873
|
-
.sort((left, right) => {
|
|
874
|
-
const leftDistance = Math.hypot(left.x - anchor.x, left.y - anchor.y)
|
|
875
|
-
const rightDistance = Math.hypot(right.x - anchor.x, right.y - anchor.y)
|
|
876
|
-
if (leftDistance !== rightDistance) return leftDistance - rightDistance
|
|
877
|
-
const leftDegree = state.nodeDegrees.get(left.id) ?? 0
|
|
878
|
-
const rightDegree = state.nodeDegrees.get(right.id) ?? 0
|
|
879
|
-
if (leftDegree !== rightDegree) return rightDegree - leftDegree
|
|
880
|
-
return left.id.localeCompare(right.id)
|
|
881
|
-
})
|
|
882
|
-
.slice(0, limit)
|
|
883
|
-
}
|
|
884
|
-
|
|
885
764
|
const edgeIdentityKey = edge => {
|
|
886
765
|
if (!edge.target) return ''
|
|
887
766
|
const pair = edge.source < edge.target
|
|
@@ -1002,8 +881,8 @@ const edgeWidthFor = (edge, selectedEdge) => {
|
|
|
1002
881
|
const drawGraphEdge = (edge) => {
|
|
1003
882
|
const selectedEdge = state.selected && (edge.source === state.selected.id || edge.target === state.selected.id)
|
|
1004
883
|
ctx.beginPath()
|
|
1005
|
-
ctx.moveTo(
|
|
1006
|
-
ctx.lineTo(
|
|
884
|
+
ctx.moveTo(edge.sourceNode.x, edge.sourceNode.y)
|
|
885
|
+
ctx.lineTo(edge.targetNode.x, edge.targetNode.y)
|
|
1007
886
|
ctx.strokeStyle = edgeStrokeFor(edge, selectedEdge)
|
|
1008
887
|
ctx.lineWidth = edgeWidthFor(edge, selectedEdge)
|
|
1009
888
|
ctx.stroke()
|
|
@@ -1017,8 +896,8 @@ const drawEdgeBatch = (edges, options) => {
|
|
|
1017
896
|
ctx.beginPath()
|
|
1018
897
|
for (let index = 0; index < edges.length; index += 1) {
|
|
1019
898
|
const edge = edges[index]
|
|
1020
|
-
ctx.moveTo(
|
|
1021
|
-
ctx.lineTo(
|
|
899
|
+
ctx.moveTo(edge.sourceNode.x, edge.sourceNode.y)
|
|
900
|
+
ctx.lineTo(edge.targetNode.x, edge.targetNode.y)
|
|
1022
901
|
}
|
|
1023
902
|
ctx.strokeStyle = options.strokeStyle
|
|
1024
903
|
ctx.lineWidth = options.lineWidth
|
|
@@ -1073,13 +952,11 @@ const shouldDrawNodeLabels = (node, isSelected, isHovered) =>
|
|
|
1073
952
|
(state.nodes.length <= largeGraphNodeThreshold && (state.transform.scale > 1.18 || state.nodes.length <= 25))
|
|
1074
953
|
|
|
1075
954
|
const drawSingleNode = (node, options = { drawLabel: true }) => {
|
|
1076
|
-
const radius = nodeRadius(node)
|
|
1077
|
-
const x =
|
|
1078
|
-
const y =
|
|
1079
|
-
const opacity = nodeRenderOpacity(node)
|
|
955
|
+
const radius = nodeRadius(node)
|
|
956
|
+
const x = node.x
|
|
957
|
+
const y = node.y
|
|
1080
958
|
const isSelected = state.selected?.id === node.id
|
|
1081
959
|
const isHovered = state.hovered?.id === node.id
|
|
1082
|
-
ctx.globalAlpha = opacity
|
|
1083
960
|
ctx.beginPath()
|
|
1084
961
|
ctx.arc(x, y, radius + (isSelected ? 7 : isHovered ? 4 : 0), 0, Math.PI * 2)
|
|
1085
962
|
ctx.fillStyle = isSelected || isHovered ? graphTheme.nodeHaloActive : graphTheme.nodeHalo
|
|
@@ -1093,7 +970,7 @@ const drawSingleNode = (node, options = { drawLabel: true }) => {
|
|
|
1093
970
|
ctx.stroke()
|
|
1094
971
|
|
|
1095
972
|
if (options.drawLabel && shouldDrawNodeLabels(node, isSelected, isHovered)) {
|
|
1096
|
-
ctx.globalAlpha =
|
|
973
|
+
ctx.globalAlpha = 1
|
|
1097
974
|
ctx.fillStyle = graphTheme.label
|
|
1098
975
|
ctx.font = '12px Inter, system-ui, sans-serif'
|
|
1099
976
|
ctx.textAlign = 'center'
|
|
@@ -1112,10 +989,10 @@ const drawNodeBatch = (nodes) => {
|
|
|
1112
989
|
if (drawHalos) {
|
|
1113
990
|
for (let index = 0; index < nodes.length; index += 1) {
|
|
1114
991
|
const node = nodes[index]
|
|
1115
|
-
const radius = nodeRadius(node)
|
|
1116
|
-
const x =
|
|
1117
|
-
const y =
|
|
1118
|
-
ctx.globalAlpha =
|
|
992
|
+
const radius = nodeRadius(node)
|
|
993
|
+
const x = node.x
|
|
994
|
+
const y = node.y
|
|
995
|
+
ctx.globalAlpha = 0.5
|
|
1119
996
|
ctx.beginPath()
|
|
1120
997
|
ctx.arc(x, y, radius + 3, 0, Math.PI * 2)
|
|
1121
998
|
ctx.fillStyle = graphTheme.nodeHalo
|
|
@@ -1126,11 +1003,10 @@ const drawNodeBatch = (nodes) => {
|
|
|
1126
1003
|
|
|
1127
1004
|
for (let index = 0; index < nodes.length; index += 1) {
|
|
1128
1005
|
const node = nodes[index]
|
|
1129
|
-
const radius = nodeRadius(node)
|
|
1130
|
-
const x =
|
|
1131
|
-
const y =
|
|
1132
|
-
|
|
1133
|
-
ctx.globalAlpha = opacity
|
|
1006
|
+
const radius = nodeRadius(node)
|
|
1007
|
+
const x = node.x
|
|
1008
|
+
const y = node.y
|
|
1009
|
+
ctx.globalAlpha = 1
|
|
1134
1010
|
ctx.beginPath()
|
|
1135
1011
|
ctx.arc(x, y, radius, 0, Math.PI * 2)
|
|
1136
1012
|
ctx.fillStyle = graphTheme.node
|
|
@@ -1167,10 +1043,10 @@ const drawGraphNodes = () => {
|
|
|
1167
1043
|
ctx.textBaseline = 'top'
|
|
1168
1044
|
for (let index = 0; index < regularNodes.length; index += 1) {
|
|
1169
1045
|
const node = regularNodes[index]
|
|
1170
|
-
const x =
|
|
1171
|
-
const y =
|
|
1172
|
-
const radius = nodeRadius(node)
|
|
1173
|
-
ctx.globalAlpha =
|
|
1046
|
+
const x = node.x
|
|
1047
|
+
const y = node.y
|
|
1048
|
+
const radius = nodeRadius(node)
|
|
1049
|
+
ctx.globalAlpha = 1
|
|
1174
1050
|
ctx.fillText(node.title.slice(0, 34), x, y + radius + 8)
|
|
1175
1051
|
}
|
|
1176
1052
|
ctx.globalAlpha = 1
|
|
@@ -1224,10 +1100,10 @@ const drawGraphLabels = nodes => {
|
|
|
1224
1100
|
ctx.textBaseline = 'top'
|
|
1225
1101
|
for (let index = 0; index < nodes.length; index += 1) {
|
|
1226
1102
|
const node = nodes[index]
|
|
1227
|
-
const x =
|
|
1228
|
-
const y =
|
|
1229
|
-
const radius = nodeRadius(node)
|
|
1230
|
-
ctx.globalAlpha =
|
|
1103
|
+
const x = node.x
|
|
1104
|
+
const y = node.y
|
|
1105
|
+
const radius = nodeRadius(node)
|
|
1106
|
+
ctx.globalAlpha = 1
|
|
1231
1107
|
ctx.fillText(node.title.slice(0, 34), x, y + radius + 8)
|
|
1232
1108
|
}
|
|
1233
1109
|
ctx.globalAlpha = 1
|
|
@@ -1435,6 +1311,11 @@ const sampleVisibleNodes = (limit = renderNodeBudget, sourceNodes = state.visibl
|
|
|
1435
1311
|
return nodes
|
|
1436
1312
|
}
|
|
1437
1313
|
|
|
1314
|
+
const sampleMassiveOverviewNodes = (limit) => {
|
|
1315
|
+
const sampled = sampleVisibleNodes(limit, state.visibleNodes)
|
|
1316
|
+
return ensureHubNodesInRenderedSet(sampled)
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1438
1319
|
const enrichSampleWithNeighbors = (nodes) => {
|
|
1439
1320
|
if (nodes.length === 0) {
|
|
1440
1321
|
return {
|
|
@@ -1724,7 +1605,7 @@ const autoFitScaleRangeByNodeCount = nodeCount => {
|
|
|
1724
1605
|
return { min: 0.0085, max: 0.36 }
|
|
1725
1606
|
}
|
|
1726
1607
|
|
|
1727
|
-
const fitView = (options = { useFiltered: true,
|
|
1608
|
+
const fitView = (options = { useFiltered: true, preferHubCenter: true }) => {
|
|
1728
1609
|
const rect = canvas.getBoundingClientRect()
|
|
1729
1610
|
const width = Math.max(rect.width, 320)
|
|
1730
1611
|
const height = Math.max(rect.height, 320)
|
|
@@ -1777,12 +1658,12 @@ const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: t
|
|
|
1777
1658
|
markRenderDirty()
|
|
1778
1659
|
}
|
|
1779
1660
|
|
|
1780
|
-
const resetView = () => fitView({ useFiltered: false,
|
|
1661
|
+
const resetView = () => fitView({ useFiltered: false, preferHubCenter: false })
|
|
1781
1662
|
|
|
1782
1663
|
const focusPrimaryHub = () => {
|
|
1783
1664
|
const hub = state.primaryHub
|
|
1784
1665
|
if (!hub) {
|
|
1785
|
-
fitView({ useFiltered: true,
|
|
1666
|
+
fitView({ useFiltered: true, preferHubCenter: true })
|
|
1786
1667
|
return
|
|
1787
1668
|
}
|
|
1788
1669
|
|
|
@@ -2161,9 +2042,9 @@ const hitNode = point => {
|
|
|
2161
2042
|
const nodes = state.renderNodes
|
|
2162
2043
|
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
2163
2044
|
const node = nodes[index]
|
|
2164
|
-
const radius = nodeRadius(node)
|
|
2165
|
-
const x =
|
|
2166
|
-
const y =
|
|
2045
|
+
const radius = nodeRadius(node)
|
|
2046
|
+
const x = node.x
|
|
2047
|
+
const y = node.y
|
|
2167
2048
|
if (Math.hypot(point.x - x, point.y - y) <= radius + 5) return node
|
|
2168
2049
|
}
|
|
2169
2050
|
return null
|
|
@@ -2176,15 +2057,6 @@ const baseNodeRadius = node => {
|
|
|
2176
2057
|
|
|
2177
2058
|
const nodeRadius = node => Math.max(baseNodeRadius(node), minNodePixelRadius / Math.max(state.transform.scale, 0.0001))
|
|
2178
2059
|
|
|
2179
|
-
const refreshRenderNodeDepthProjection = () => {
|
|
2180
|
-
state.renderNodeDepthProjectionById = new Map()
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
const projectedNode = node => state.renderNodeDepthProjectionById.get(node.id) ?? null
|
|
2184
|
-
const nodeRenderX = node => projectedNode(node)?.x ?? node.x
|
|
2185
|
-
const nodeRenderY = node => projectedNode(node)?.y ?? node.y
|
|
2186
|
-
const nodeRenderScale = node => projectedNode(node)?.scale ?? 1
|
|
2187
|
-
const nodeRenderOpacity = node => projectedNode(node)?.opacity ?? 1
|
|
2188
2060
|
const worldViewportBounds = () => {
|
|
2189
2061
|
const width = Math.max(state.viewport.width, 320)
|
|
2190
2062
|
const height = Math.max(state.viewport.height, 320)
|
|
@@ -2264,9 +2136,17 @@ const computeRenderVisibility = () => {
|
|
|
2264
2136
|
}
|
|
2265
2137
|
|
|
2266
2138
|
if (state.visibleNodes.length > massiveGraphNodeThreshold) {
|
|
2267
|
-
const viewportNodes = viewportNodesFromSpatialIndex(viewport)
|
|
2268
|
-
const sourceNodes = viewportNodes.length > 0 ? viewportNodes : state.visibleNodes
|
|
2269
2139
|
const sampleLimit = nodeBudgetForScale(state.transform.scale)
|
|
2140
|
+
if (state.transform.scale < massiveOverviewScaleThreshold) {
|
|
2141
|
+
const overviewNodes = sampleMassiveOverviewNodes(sampleLimit)
|
|
2142
|
+
const overviewIds = new Set(overviewNodes.map((node) => node.id))
|
|
2143
|
+
state.renderNodes = overviewNodes
|
|
2144
|
+
state.renderEdges = withMeshEdges(overviewNodes, collectVisibleEdgesForNodes(overviewIds))
|
|
2145
|
+
return
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
const viewportNodes = viewportNodesFromSpatialIndex(viewport)
|
|
2149
|
+
const sourceNodes = viewportNodes.length > 0 ? viewportNodes : sampleMassiveOverviewNodes(sampleLimit)
|
|
2270
2150
|
const carryMargin = Math.max(240, Math.min(1200, 340 / Math.max(state.transform.scale, 0.0001)))
|
|
2271
2151
|
const carryViewport = expandViewportBounds(viewport, carryMargin)
|
|
2272
2152
|
const carryOverLimit = Math.max(180, Math.min(sampleLimit, Math.floor(sampleLimit * 0.5)))
|
|
@@ -2441,10 +2321,11 @@ const render = now => {
|
|
|
2441
2321
|
|
|
2442
2322
|
computeRenderVisibility()
|
|
2443
2323
|
tick(delta, now)
|
|
2444
|
-
refreshRenderNodeDepthProjection()
|
|
2445
2324
|
const hasVisibleNodeOnScreen = state.renderNodes.some((node) => isNodeVisibleOnScreen(node, width, height))
|
|
2446
2325
|
const manualZoomGuardActive = now - state.lastManualZoomAt < zoomRecoveryGuardMs
|
|
2447
|
-
const allowViewportAutoRecovery =
|
|
2326
|
+
const allowViewportAutoRecovery =
|
|
2327
|
+
state.nodes.length <= massiveGraphNodeThreshold ||
|
|
2328
|
+
state.transform.scale >= massiveOverviewScaleThreshold
|
|
2448
2329
|
if (allowViewportAutoRecovery && !hasVisibleNodeOnScreen && state.renderNodes.length > 0 && !manualZoomGuardActive) {
|
|
2449
2330
|
state.offscreenFrameCount += 1
|
|
2450
2331
|
if (state.offscreenFrameCount >= 22 && !state.recoveringViewport) {
|
package/package.json
CHANGED