@andespindola/brainlink 0.1.0-beta.52 → 0.1.0-beta.53
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.
|
@@ -52,6 +52,7 @@ const state = {
|
|
|
52
52
|
overviewClusters: [],
|
|
53
53
|
macroCenter: { x: 0, y: 0 },
|
|
54
54
|
macroRepresentative: null,
|
|
55
|
+
primaryHub: null,
|
|
55
56
|
filterWorker: null,
|
|
56
57
|
filterReady: false,
|
|
57
58
|
lastHoverHitAt: 0,
|
|
@@ -296,6 +297,7 @@ const recomputeVisibility = () => {
|
|
|
296
297
|
}
|
|
297
298
|
: { x: 0, y: 0 }
|
|
298
299
|
state.macroRepresentative = resolveMacroRepresentative(nodes)
|
|
300
|
+
state.primaryHub = rankedHubNodes()[0] ?? null
|
|
299
301
|
markRenderDirty()
|
|
300
302
|
}
|
|
301
303
|
|
|
@@ -679,7 +681,7 @@ const autoFitScaleRangeByNodeCount = nodeCount => {
|
|
|
679
681
|
return { min: 0.0008, max: 0.24 }
|
|
680
682
|
}
|
|
681
683
|
|
|
682
|
-
const fitView = (options = { useFiltered: true, macro: false }) => {
|
|
684
|
+
const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: true }) => {
|
|
683
685
|
const rect = canvas.getBoundingClientRect()
|
|
684
686
|
const width = Math.max(rect.width, 320)
|
|
685
687
|
const height = Math.max(rect.height, 320)
|
|
@@ -716,8 +718,12 @@ const fitView = (options = { useFiltered: true, macro: false }) => {
|
|
|
716
718
|
: nodes.length > massiveGraphNodeThreshold
|
|
717
719
|
? clampScale(Math.min(baselineScale, massiveAutoFitMacroScale))
|
|
718
720
|
: baselineScale
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
+
const hubCenter =
|
|
722
|
+
options.preferHubCenter && state.primaryHub && nodes.some((node) => node.id === state.primaryHub.id)
|
|
723
|
+
? state.primaryHub
|
|
724
|
+
: null
|
|
725
|
+
const centerX = hubCenter ? hubCenter.x : (bounds.minX + bounds.maxX) / 2
|
|
726
|
+
const centerY = hubCenter ? hubCenter.y : (bounds.minY + bounds.maxY) / 2
|
|
721
727
|
|
|
722
728
|
state.transform = {
|
|
723
729
|
x: clampTransformCoordinate(width / 2 - centerX * scale),
|
|
@@ -729,7 +735,7 @@ const fitView = (options = { useFiltered: true, macro: false }) => {
|
|
|
729
735
|
markRenderDirty()
|
|
730
736
|
}
|
|
731
737
|
|
|
732
|
-
const resetView = () => fitView({ useFiltered: false, macro: true })
|
|
738
|
+
const resetView = () => fitView({ useFiltered: false, macro: true, preferHubCenter: true })
|
|
733
739
|
|
|
734
740
|
const createLayout = graph => {
|
|
735
741
|
const nodeRows = Array.isArray(graph.nodes) ? graph.nodes : []
|
|
@@ -20,6 +20,7 @@ const segmentAngles = {
|
|
|
20
20
|
Evaluation: 2.08,
|
|
21
21
|
Security: 2.82
|
|
22
22
|
};
|
|
23
|
+
const hubTitlePattern = /\b(memory\s*hub|knowledge\s*root|moc|map)\b/i;
|
|
23
24
|
const hashText = (value) => Array.from(value).reduce((hash, char) => ((hash << 5) - hash + char.charCodeAt(0)) | 0, 0);
|
|
24
25
|
const jitter = (value, range) => {
|
|
25
26
|
const normalized = Math.abs(hashText(value) % 1000) / 1000;
|
|
@@ -62,6 +63,44 @@ const byDegreeThenTitle = (degrees) => (left, right) => {
|
|
|
62
63
|
const degreeDelta = (degrees.get(right.id) ?? 0) - (degrees.get(left.id) ?? 0);
|
|
63
64
|
return degreeDelta === 0 ? byTitle(left, right) : degreeDelta;
|
|
64
65
|
};
|
|
66
|
+
const hubScore = (node) => {
|
|
67
|
+
const title = node.title.trim().toLowerCase();
|
|
68
|
+
if (title === 'memory hub')
|
|
69
|
+
return 5;
|
|
70
|
+
if (title === 'knowledge root')
|
|
71
|
+
return 4;
|
|
72
|
+
if (/\bmoc\b/i.test(node.title))
|
|
73
|
+
return 3;
|
|
74
|
+
return hubTitlePattern.test(node.title) ? 2 : 0;
|
|
75
|
+
};
|
|
76
|
+
const selectPrimaryHubId = (nodes, degrees) => {
|
|
77
|
+
const ranked = [...nodes]
|
|
78
|
+
.filter((node) => hubScore(node) > 0)
|
|
79
|
+
.sort((left, right) => {
|
|
80
|
+
const scoreDelta = hubScore(right) - hubScore(left);
|
|
81
|
+
if (scoreDelta !== 0)
|
|
82
|
+
return scoreDelta;
|
|
83
|
+
const degreeDelta = (degrees.get(right.id) ?? 0) - (degrees.get(left.id) ?? 0);
|
|
84
|
+
if (degreeDelta !== 0)
|
|
85
|
+
return degreeDelta;
|
|
86
|
+
return left.title.localeCompare(right.title);
|
|
87
|
+
});
|
|
88
|
+
return ranked[0]?.id ?? null;
|
|
89
|
+
};
|
|
90
|
+
const centerLayoutByNode = (nodes, nodeId) => {
|
|
91
|
+
if (!nodeId) {
|
|
92
|
+
return nodes;
|
|
93
|
+
}
|
|
94
|
+
const anchor = nodes.find((node) => node.id === nodeId);
|
|
95
|
+
if (!anchor) {
|
|
96
|
+
return nodes;
|
|
97
|
+
}
|
|
98
|
+
return nodes.map((node) => ({
|
|
99
|
+
...node,
|
|
100
|
+
x: node.x - anchor.x,
|
|
101
|
+
y: node.y - anchor.y
|
|
102
|
+
}));
|
|
103
|
+
};
|
|
65
104
|
const naturalSegmentSeed = (node) => groupKey(node) === '00-maps' || /\b(moc|map)\b/i.test(node.title);
|
|
66
105
|
const segmentName = (node) => node.title.replace(/^MOC\s+/i, '').replace(/\s+Memory Map$/i, '').trim() || node.title;
|
|
67
106
|
const collectComponent = (adjacency, startId, visited) => {
|
|
@@ -246,8 +285,10 @@ export const createCauliflowerGraphLayout = (graph) => {
|
|
|
246
285
|
const segmentGroups = Array.from(groupNodesBySegment(graph.nodes, segments).entries())
|
|
247
286
|
.sort(([left], [right]) => left.localeCompare(right));
|
|
248
287
|
const nodes = relaxCollisions(segmentGroups.flatMap(createSegmentNodes(segments, degrees, segmentGroups.length)));
|
|
288
|
+
const primaryHubId = selectPrimaryHubId(graph.nodes, degrees);
|
|
289
|
+
const centeredNodes = centerLayoutByNode(nodes, primaryHubId);
|
|
249
290
|
return {
|
|
250
|
-
nodes,
|
|
291
|
+
nodes: centeredNodes,
|
|
251
292
|
edges: graph.edges
|
|
252
293
|
};
|
|
253
294
|
};
|
package/package.json
CHANGED