@andespindola/brainlink 0.1.0-beta.46 → 0.1.0-beta.48
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.
|
@@ -7,6 +7,8 @@ const renderNodeBudget = 900
|
|
|
7
7
|
const renderEdgeBudget = 2400
|
|
8
8
|
const clusterActivationNodeThreshold = 600
|
|
9
9
|
const clusterZoomThreshold = 0.18
|
|
10
|
+
const macroGalaxyZoomThreshold = 0.012
|
|
11
|
+
const massiveAutoFitMacroScale = 0.006
|
|
10
12
|
const clusterCellPixelSize = 64
|
|
11
13
|
const minNodePixelRadius = 2.3
|
|
12
14
|
const viewportPaddingPx = 280
|
|
@@ -45,6 +47,8 @@ const state = {
|
|
|
45
47
|
visibleNodeSpatial: { cellSize: 220, minX: 0, minY: 0, maxX: 0, maxY: 0, buckets: new Map() },
|
|
46
48
|
visibleEdgeByNode: new Map(),
|
|
47
49
|
overviewClusters: [],
|
|
50
|
+
macroCenter: { x: 0, y: 0 },
|
|
51
|
+
macroRepresentative: null,
|
|
48
52
|
filterWorker: null,
|
|
49
53
|
filterReady: false,
|
|
50
54
|
lastHoverHitAt: 0
|
|
@@ -245,6 +249,26 @@ const filteredNodes = () => {
|
|
|
245
249
|
return withPersistentHubNodes(localFilteredNodes(query))
|
|
246
250
|
}
|
|
247
251
|
|
|
252
|
+
const resolveMacroRepresentative = (nodes) => {
|
|
253
|
+
if (nodes.length === 0) {
|
|
254
|
+
return null
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let best = nodes[0]
|
|
258
|
+
let bestDegree = state.nodeDegrees.get(best.id) ?? 0
|
|
259
|
+
|
|
260
|
+
for (let index = 1; index < nodes.length; index += 1) {
|
|
261
|
+
const node = nodes[index]
|
|
262
|
+
const degree = state.nodeDegrees.get(node.id) ?? 0
|
|
263
|
+
if (degree > bestDegree) {
|
|
264
|
+
best = node
|
|
265
|
+
bestDegree = degree
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return best
|
|
270
|
+
}
|
|
271
|
+
|
|
248
272
|
const recomputeVisibility = () => {
|
|
249
273
|
const nodes = filteredNodes()
|
|
250
274
|
const ids = new Set(nodes.map(node => node.id))
|
|
@@ -260,6 +284,14 @@ const recomputeVisibility = () => {
|
|
|
260
284
|
state.visibleNodeSpatial = createSpatialIndex(nodes)
|
|
261
285
|
state.visibleEdgeByNode = createVisibleEdgeLookup(limitedEdges)
|
|
262
286
|
state.overviewClusters = nodes.length > massiveGraphNodeThreshold ? buildOverviewClusters(nodes) : []
|
|
287
|
+
const bounds = graphBounds(nodes)
|
|
288
|
+
state.macroCenter = bounds
|
|
289
|
+
? {
|
|
290
|
+
x: (bounds.minX + bounds.maxX) / 2,
|
|
291
|
+
y: (bounds.minY + bounds.maxY) / 2
|
|
292
|
+
}
|
|
293
|
+
: { x: 0, y: 0 }
|
|
294
|
+
state.macroRepresentative = resolveMacroRepresentative(nodes)
|
|
263
295
|
markRenderDirty()
|
|
264
296
|
}
|
|
265
297
|
|
|
@@ -500,17 +532,17 @@ const fallbackViewportNodes = () => {
|
|
|
500
532
|
return nodes
|
|
501
533
|
}
|
|
502
534
|
|
|
503
|
-
const sampleVisibleNodes = (limit = renderNodeBudget) => {
|
|
504
|
-
if (
|
|
535
|
+
const sampleVisibleNodes = (limit = renderNodeBudget, sourceNodes = state.visibleNodes) => {
|
|
536
|
+
if (sourceNodes.length === 0 || limit <= 0) {
|
|
505
537
|
return []
|
|
506
538
|
}
|
|
507
539
|
|
|
508
540
|
const nodes = []
|
|
509
|
-
const maxNodes = Math.min(Math.max(limit, 1),
|
|
510
|
-
const step = Math.max(1, Math.ceil(
|
|
541
|
+
const maxNodes = Math.min(Math.max(limit, 1), sourceNodes.length)
|
|
542
|
+
const step = Math.max(1, Math.ceil(sourceNodes.length / maxNodes))
|
|
511
543
|
|
|
512
|
-
for (let index = 0; index <
|
|
513
|
-
nodes.push(
|
|
544
|
+
for (let index = 0; index < sourceNodes.length && nodes.length < maxNodes; index += step) {
|
|
545
|
+
nodes.push(sourceNodes[index])
|
|
514
546
|
}
|
|
515
547
|
|
|
516
548
|
if (state.selected && !nodes.find(node => node.id === state.selected.id)) {
|
|
@@ -607,7 +639,10 @@ const fitView = (options = { useFiltered: true }) => {
|
|
|
607
639
|
const fitScale = Math.min(scaleX, scaleY)
|
|
608
640
|
const biasedScale = clampScale(fitScale * fitScaleBiasByNodeCount(nodes.length))
|
|
609
641
|
const scaleRange = autoFitScaleRangeByNodeCount(nodes.length)
|
|
610
|
-
const
|
|
642
|
+
const baselineScale = clampScale(Math.min(scaleRange.max, Math.max(scaleRange.min, biasedScale)))
|
|
643
|
+
const scale = nodes.length > massiveGraphNodeThreshold
|
|
644
|
+
? clampScale(Math.min(baselineScale, massiveAutoFitMacroScale))
|
|
645
|
+
: baselineScale
|
|
611
646
|
const centerX = (bounds.minX + bounds.maxX) / 2
|
|
612
647
|
const centerY = (bounds.minY + bounds.maxY) / 2
|
|
613
648
|
|
|
@@ -981,14 +1016,34 @@ const computeRenderVisibility = () => {
|
|
|
981
1016
|
if (state.visibleNodes.length > massiveGraphNodeThreshold) {
|
|
982
1017
|
const viewportNodes = viewportNodesFromSpatialIndex(viewport)
|
|
983
1018
|
const sourceNodes = viewportNodes.length > 0 ? viewportNodes : state.visibleNodes
|
|
1019
|
+
if (state.transform.scale <= macroGalaxyZoomThreshold) {
|
|
1020
|
+
const representative = state.macroRepresentative ?? sourceNodes[0] ?? null
|
|
1021
|
+
if (representative) {
|
|
1022
|
+
state.renderClusters = [
|
|
1023
|
+
{
|
|
1024
|
+
id: 'macro-galaxy',
|
|
1025
|
+
x: state.macroCenter.x,
|
|
1026
|
+
y: state.macroCenter.y,
|
|
1027
|
+
count: sourceNodes.length,
|
|
1028
|
+
representative
|
|
1029
|
+
}
|
|
1030
|
+
]
|
|
1031
|
+
state.renderNodes = [representative]
|
|
1032
|
+
} else {
|
|
1033
|
+
state.renderClusters = []
|
|
1034
|
+
state.renderNodes = []
|
|
1035
|
+
}
|
|
1036
|
+
state.renderEdges = []
|
|
1037
|
+
return
|
|
1038
|
+
}
|
|
984
1039
|
const sampleLimit = nodeBudgetForScale(state.transform.scale)
|
|
985
1040
|
const sampled = sourceNodes.length > sampleLimit
|
|
986
|
-
? sampleVisibleNodes(Math.min(sampleLimit, renderNodeBudget))
|
|
1041
|
+
? sampleVisibleNodes(Math.min(sampleLimit, renderNodeBudget), sourceNodes)
|
|
987
1042
|
: sourceNodes.slice(0, Math.min(sourceNodes.length, renderNodeBudget))
|
|
988
1043
|
const sampledIds = new Set(sampled.map((node) => node.id))
|
|
989
1044
|
state.renderClusters = []
|
|
990
1045
|
state.renderNodes = sampled
|
|
991
|
-
state.renderEdges = state.transform.scale >= 0.
|
|
1046
|
+
state.renderEdges = state.transform.scale >= 0.1 ? collectVisibleEdgesForNodes(sampledIds) : []
|
|
992
1047
|
return
|
|
993
1048
|
}
|
|
994
1049
|
|
|
@@ -1143,9 +1198,15 @@ const render = now => {
|
|
|
1143
1198
|
} else {
|
|
1144
1199
|
state.offscreenFrameCount = 0
|
|
1145
1200
|
}
|
|
1201
|
+
const minimumEdgeScale =
|
|
1202
|
+
state.nodes.length > massiveGraphNodeThreshold
|
|
1203
|
+
? 0.1
|
|
1204
|
+
: state.nodes.length > largeGraphNodeThreshold
|
|
1205
|
+
? 0.16
|
|
1206
|
+
: 0
|
|
1146
1207
|
const drawEdges =
|
|
1147
1208
|
state.renderClusters.length === 0 &&
|
|
1148
|
-
|
|
1209
|
+
state.transform.scale >= minimumEdgeScale
|
|
1149
1210
|
if (drawEdges) {
|
|
1150
1211
|
state.renderEdges.forEach(edge => {
|
|
1151
1212
|
const selectedEdge = state.selected && (edge.source === state.selected.id || edge.target === state.selected.id)
|
|
@@ -1161,19 +1222,22 @@ const render = now => {
|
|
|
1161
1222
|
if (state.renderClusters.length > 0) {
|
|
1162
1223
|
const safeScale = Math.max(state.transform.scale, 0.0001)
|
|
1163
1224
|
state.renderClusters.forEach(cluster => {
|
|
1164
|
-
const
|
|
1225
|
+
const isMacro = cluster.id === 'macro-galaxy'
|
|
1226
|
+
const radiusPx = isMacro
|
|
1227
|
+
? 10
|
|
1228
|
+
: Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
1165
1229
|
const radius = radiusPx / safeScale
|
|
1166
|
-
const haloRadius = (radiusPx + 4) / safeScale
|
|
1230
|
+
const haloRadius = (radiusPx + (isMacro ? 8 : 4)) / safeScale
|
|
1167
1231
|
ctx.beginPath()
|
|
1168
1232
|
ctx.arc(cluster.x, cluster.y, haloRadius, 0, Math.PI * 2)
|
|
1169
|
-
ctx.fillStyle = graphTheme.nodeHalo
|
|
1233
|
+
ctx.fillStyle = isMacro ? 'rgba(243, 247, 251, 0.28)' : graphTheme.nodeHalo
|
|
1170
1234
|
ctx.fill()
|
|
1171
1235
|
ctx.beginPath()
|
|
1172
1236
|
ctx.arc(cluster.x, cluster.y, radius, 0, Math.PI * 2)
|
|
1173
|
-
ctx.fillStyle = graphTheme.node
|
|
1237
|
+
ctx.fillStyle = isMacro ? '#f3f7fb' : graphTheme.node
|
|
1174
1238
|
ctx.fill()
|
|
1175
1239
|
ctx.lineWidth = 1.4 / safeScale
|
|
1176
|
-
ctx.strokeStyle = graphTheme.nodeStroke
|
|
1240
|
+
ctx.strokeStyle = isMacro ? '#ffffff' : graphTheme.nodeStroke
|
|
1177
1241
|
ctx.stroke()
|
|
1178
1242
|
// Keep cluster markers minimal and faster to draw on large graphs.
|
|
1179
1243
|
})
|
package/package.json
CHANGED