()
| 3702 | } |
| 3703 | |
| 3704 | function renderWikiGraph() { |
| 3705 | const target = $("wikiGraph"); |
| 3706 | const graph = state.wikiGraph || {}; |
| 3707 | const hasBackendGraph = Boolean(graph.nodes?.length); |
| 3708 | const graphNodes = hasBackendGraph ? graph.nodes : state.wikiNodes.length ? state.wikiNodes : DEMO_MODE ? demoWikiNodes : []; |
| 3709 | const graphEdges = hasBackendGraph ? graph.edges || [] : DEMO_MODE ? demoWikiEdges : []; |
| 3710 | const layout = layoutGraph(graphNodes, graphEdges); |
| 3711 | if (!layout.nodes.length) { |
| 3712 | target.innerHTML = ` |
| 3713 | <div class="wiki-empty-state"> |
| 3714 | <strong>${escapeHtml(ui().wiki.emptyGraphTitle)}</strong> |
| 3715 | <p>${escapeHtml(ui().wiki.emptyGraphBody)}</p> |
| 3716 | </div> |
| 3717 | `; |
| 3718 | return; |
| 3719 | } |
| 3720 | const spread = state.wikiGraphTransform.scale; |
| 3721 | const visualNodes = layout.nodes.map((node) => ({ |
| 3722 | ...node, |
| 3723 | x: 50 + (Number(node.x) - 50) * spread, |
| 3724 | y: 50 + (Number(node.y) - 50) * spread |
| 3725 | })); |
| 3726 | const byId = Object.fromEntries(visualNodes.map((node) => [node.id, node])); |
| 3727 | const lineHtml = layout.edges.map((edge, index) => { |
| 3728 | const a = byId[String(edge.src_id)]; |
| 3729 | const b = byId[String(edge.dst_id)]; |
| 3730 | if (!a || !b) return ""; |
| 3731 | const weightClass = graphWeight(edge) >= 0.78 || a.id === layout.centerId || b.id === layout.centerId |
| 3732 | ? "strong" |
| 3733 | : graphWeight(edge) >= 0.35 ? "medium" : "light"; |
| 3734 | const curve = (index % 2 === 0 ? 4 : -4) * Math.max(0.8, Math.min(spread, 1.8)); |
| 3735 | const midX = (a.x + b.x) / 2 + curve; |
| 3736 | const midY = (a.y + b.y) / 2 - curve; |
| 3737 | return `<path class="graph-svg-link ${weightClass}" d="M ${a.x} ${a.y} Q ${midX} ${midY} ${b.x} ${b.y}"></path>`; |
| 3738 | }).join(""); |
| 3739 | const activeNodeId = state.currentWikiNodeId || layout.centerId; |
| 3740 | const nodeZoom = Math.max(0.82, Math.min(1.18, 0.9 + spread * 0.1)); |
| 3741 | const labelZoom = Math.max(0.9, Math.min(1.22, 0.94 + spread * 0.08)); |
| 3742 | const nodeHtml = visualNodes.map((node) => ` |
| 3743 | <button |
| 3744 | class="graph-node ${node.type} ${node.size || "medium"} ${node.id === activeNodeId ? "active" : ""}" |
| 3745 | data-node-id="${escapeHtml(graphId(node))}" |
| 3746 | data-real-node-id="${escapeHtml(node.real_node_id || "")}" |
| 3747 | data-node-title="${escapeHtml(rawGraphTitle(node))}" |
| 3748 | data-node-body="${escapeHtml(rawGraphBody(node))}" |
| 3749 | title="${escapeHtml(graphTitle(node))}" |
| 3750 | style="left:${node.x}%;top:${node.y}%;--node-zoom:${nodeZoom};--label-zoom:${labelZoom}" |
| 3751 | type="button" |
| 3752 | > |
| 3753 | <span class="node-dot"></span> |
| 3754 | <span class="node-label">${escapeHtml(graphTitle(node))}</span> |
| 3755 | </button> |
| 3756 | `).join(""); |
| 3757 | target.classList.toggle("real", hasBackendGraph); |
| 3758 | const totalNodes = Number(state.wikiStats?.nodes || layout.sourceCount || 0); |
| 3759 | const graphNote = hasBackendGraph |
| 3760 | ? ui().wiki.graphRealNote(layout.nodes.length, totalNodes, layout.sourceEdgeCount) |
| 3761 | : state.wikiNodes.length |
no test coverage detected