(nodes, edges)
| 3638 | } |
| 3639 | |
| 3640 | function layoutGraph(nodes, edges) { |
| 3641 | const sourceNodes = uniqueGraphNodes((nodes || []).filter((node) => graphId(node).trim())); |
| 3642 | const sourceNodeIds = new Set(sourceNodes.map((node) => graphId(node))); |
| 3643 | const sourceEdges = (edges || []) |
| 3644 | .map(normalizeGraphEdge) |
| 3645 | .filter((edge) => sourceNodeIds.has(edge.src_id) && sourceNodeIds.has(edge.dst_id)); |
| 3646 | const degree = graphDegreeMap(sourceNodes, sourceEdges); |
| 3647 | const groups = { |
| 3648 | topic: { x: 26, y: 31, nodes: [] }, |
| 3649 | paper: { x: 42, y: 64, nodes: [] }, |
| 3650 | method: { x: 70, y: 58, nodes: [] }, |
| 3651 | frontier: { x: 74, y: 28, nodes: [] } |
| 3652 | }; |
| 3653 | sourceNodes |
| 3654 | .slice() |
| 3655 | .sort((a, b) => (degree[graphId(b)] || 0) - (degree[graphId(a)] || 0)) |
| 3656 | .forEach((node) => { |
| 3657 | const type = graphType(node); |
| 3658 | (groups[type] || groups.topic).nodes.push(node); |
| 3659 | }); |
| 3660 | |
| 3661 | const mapNodes = []; |
| 3662 | Object.entries(groups).forEach(([type, group]) => { |
| 3663 | const count = Math.max(1, group.nodes.length); |
| 3664 | group.nodes.forEach((node, index) => { |
| 3665 | const ring = Math.floor(index / 18); |
| 3666 | const slot = index % 18; |
| 3667 | const angle = (slot / Math.min(18, count)) * Math.PI * 2 + ring * 0.38; |
| 3668 | const radiusX = 7 + ring * 6 + (type === "paper" ? 3 : 0); |
| 3669 | const radiusY = 6 + ring * 4.5 + (type === "paper" ? 2 : 0); |
| 3670 | const id = graphId(node); |
| 3671 | const score = degree[id] || 0; |
| 3672 | mapNodes.push({ |
| 3673 | ...node, |
| 3674 | id, |
| 3675 | node_id: id, |
| 3676 | type, |
| 3677 | x: clamp(group.x + Math.cos(angle) * radiusX, 5, 94), |
| 3678 | y: clamp(group.y + Math.sin(angle) * radiusY, 8, 91), |
| 3679 | size: score >= 2.5 ? "large" : score >= 1.0 ? "medium" : "small", |
| 3680 | real_node_id: id |
| 3681 | }); |
| 3682 | }); |
| 3683 | }); |
| 3684 | const map = { |
| 3685 | nodes: mapNodes, |
| 3686 | edges: sourceEdges, |
| 3687 | sourceNodes, |
| 3688 | sourceEdges, |
| 3689 | byId: Object.fromEntries(mapNodes.map((node) => [node.id, node])) |
| 3690 | }; |
| 3691 | state.wikiMap = map; |
| 3692 | const focusId = state.currentWikiNodeId && map.byId[state.currentWikiNodeId] |
| 3693 | ? state.currentWikiNodeId |
| 3694 | : pickWikiFocusNodeId(); |
| 3695 | return { |
| 3696 | nodes: map.nodes.map((node) => ({ ...node, x: node.x, y: node.y, type: node.type, size: node.size })), |
| 3697 | edges: map.edges, |
no test coverage detected