#include "viz/graph_layouts.h" #include "viz/graph_types.h" #include #include #include #include #include namespace graph { // --------------------------------------------------------------------------- // layout_grid // --------------------------------------------------------------------------- void layout_grid(GraphData& g, float spacing) { if (g.node_count <= 0) return; int cols = (int)std::ceil(std::sqrt((float)g.node_count)); if (cols < 1) cols = 1; int rows = (g.node_count + cols - 1) / cols; float ox = -0.5f * (cols - 1) * spacing; float oy = -0.5f * (rows - 1) * spacing; for (int i = 0; i < g.node_count; ++i) { GraphNode& n = g.nodes[i]; if (n.flags & NF_PINNED) continue; int col = i % cols; int row = i / cols; n.x = ox + col * spacing; n.y = oy + row * spacing; n.vx = 0.0f; n.vy = 0.0f; } g.update_bounds(); } // --------------------------------------------------------------------------- // layout_circular // --------------------------------------------------------------------------- void layout_circular(GraphData& g, float radius) { if (g.node_count <= 0) return; const float two_pi = 6.28318530718f; for (int i = 0; i < g.node_count; ++i) { GraphNode& n = g.nodes[i]; if (n.flags & NF_PINNED) continue; float angle = two_pi * (float)i / (float)g.node_count; n.x = radius * std::cos(angle); n.y = radius * std::sin(angle); n.vx = 0.0f; n.vy = 0.0f; } g.update_bounds(); } // --------------------------------------------------------------------------- // layout_random // --------------------------------------------------------------------------- void layout_random(GraphData& g, float spread) { if (g.node_count <= 0) return; for (int i = 0; i < g.node_count; ++i) { GraphNode& n = g.nodes[i]; if (n.flags & NF_PINNED) continue; n.x = spread * (2.0f * (float)rand() / (float)RAND_MAX - 1.0f); n.y = spread * (2.0f * (float)rand() / (float)RAND_MAX - 1.0f); n.vx = 0.0f; n.vy = 0.0f; } g.update_bounds(); } // --------------------------------------------------------------------------- // layout_radial — BFS desde root, anillos concentricos por hop // --------------------------------------------------------------------------- void layout_radial(GraphData& g, int root_node, float ring_spacing) { if (g.node_count <= 0) return; if (root_node < 0 || root_node >= g.node_count) root_node = 0; // Adyacencia no dirigida via CSR temporal — para grafos pequenios/medios // basta con vector>. std::vector> adj(g.node_count); for (int e = 0; e < g.edge_count; ++e) { int s = (int)g.edges[e].source; int t = (int)g.edges[e].target; if (s < 0 || s >= g.node_count) continue; if (t < 0 || t >= g.node_count) continue; adj[s].push_back(t); adj[t].push_back(s); } // BFS para asignar hop (-1 = no visitado todavia) std::vector hop(g.node_count, -1); hop[root_node] = 0; std::queue q; q.push(root_node); int max_hop = 0; while (!q.empty()) { int u = q.front(); q.pop(); for (int v : adj[u]) { if (hop[v] == -1) { hop[v] = hop[u] + 1; if (hop[v] > max_hop) max_hop = hop[v]; q.push(v); } } } // Nodos no alcanzables van al hop max_hop + 1 int orphan_hop = max_hop + 1; bool has_orphans = false; for (int i = 0; i < g.node_count; ++i) { if (hop[i] == -1) { hop[i] = orphan_hop; has_orphans = true; } } // Agrupar indices por hop int total_hops = (has_orphans ? orphan_hop : max_hop) + 1; std::vector> by_hop(total_hops); for (int i = 0; i < g.node_count; ++i) by_hop[hop[i]].push_back(i); const float two_pi = 6.28318530718f; for (int k = 0; k < total_hops; ++k) { const auto& ring = by_hop[k]; if (ring.empty()) continue; if (k == 0) { // root en el centro GraphNode& n = g.nodes[ring[0]]; if (!(n.flags & NF_PINNED)) { n.x = 0.0f; n.y = 0.0f; n.vx = 0.0f; n.vy = 0.0f; } continue; } float radius = (float)k * ring_spacing; int count = (int)ring.size(); for (int j = 0; j < count; ++j) { GraphNode& n = g.nodes[ring[j]]; if (n.flags & NF_PINNED) continue; float angle = two_pi * (float)j / (float)count; n.x = radius * std::cos(angle); n.y = radius * std::sin(angle); n.vx = 0.0f; n.vy = 0.0f; } } g.update_bounds(); } // --------------------------------------------------------------------------- // layout_hierarchical — niveles por longest-path BFS + baricentro greedy // --------------------------------------------------------------------------- void layout_hierarchical(GraphData& g, int direction, float layer_spacing, float node_spacing) { if (g.node_count <= 0) return; const int N = g.node_count; // 1) Calcular in-degree para encontrar las raices std::vector in_deg(N, 0); std::vector> out_adj(N); std::vector> in_adj(N); for (int e = 0; e < g.edge_count; ++e) { int s = (int)g.edges[e].source; int t = (int)g.edges[e].target; if (s < 0 || s >= N || t < 0 || t >= N) continue; if (s == t) continue; out_adj[s].push_back(t); in_adj[t].push_back(s); in_deg[t]++; } // 2) Asignar nivel: longest-path desde nodos sin in-edges. BFS multi-source // con relax: level[v] = max(level[v], level[u]+1). std::vector level(N, 0); std::vector order; order.reserve(N); std::queue q; for (int i = 0; i < N; ++i) { if (in_deg[i] == 0) { q.push(i); } } // Si todo el grafo tiene ciclos (no hay raices), forzar nodo 0 como raiz if (q.empty()) { q.push(0); level[0] = 0; } // BFS con propagacion. Para evitar bucles infinitos en grafos ciclicos, // limitamos la profundidad a N. std::vector visited(N, 0); while (!q.empty()) { int u = q.front(); q.pop(); if (visited[u]++ > N) continue; for (int v : out_adj[u]) { int new_lv = level[u] + 1; if (new_lv > level[v]) { level[v] = new_lv; if (level[v] < N) q.push(v); } } } int max_level = 0; for (int i = 0; i < N; ++i) { if (level[i] > max_level) max_level = level[i]; } // 3) Agrupar nodos por nivel std::vector> by_level(max_level + 1); for (int i = 0; i < N; ++i) by_level[level[i]].push_back(i); // 4) Reducir cruces: por cada nivel L > 0 ordenar por baricentro de los // indices (en el array del nivel L-1) de sus padres. Greedy, no optimo. std::vector pos_in_level(N, 0); for (int j = 0; j < (int)by_level[0].size(); ++j) pos_in_level[by_level[0][j]] = j; for (int L = 1; L <= max_level; ++L) { auto& cur = by_level[L]; std::vector bary(cur.size(), 0.0f); for (size_t i = 0; i < cur.size(); ++i) { int v = cur[i]; float sum = 0.0f; int cnt = 0; for (int p : in_adj[v]) { if (level[p] == L - 1) { sum += (float)pos_in_level[p]; cnt++; } } bary[i] = (cnt > 0) ? (sum / (float)cnt) : (float)i; } // Ordenar `cur` por bary std::vector idx(cur.size()); for (size_t i = 0; i < idx.size(); ++i) idx[i] = i; std::sort(idx.begin(), idx.end(), [&](size_t a, size_t b){ if (bary[a] != bary[b]) return bary[a] < bary[b]; return cur[a] < cur[b]; }); std::vector reordered(cur.size()); for (size_t i = 0; i < idx.size(); ++i) reordered[i] = cur[idx[i]]; cur = std::move(reordered); for (int j = 0; j < (int)cur.size(); ++j) pos_in_level[cur[j]] = j; } // 5) Posiciones. layer_axis = nivel * layer_spacing; transverse_axis = // (j - (size-1)/2) * node_spacing (centrado). auto place = [&](int node_idx, float layer_axis, float transverse_axis) { GraphNode& n = g.nodes[node_idx]; if (n.flags & NF_PINNED) return; float lx = 0.0f, ly = 0.0f; switch (direction) { case 0: // LR lx = layer_axis; ly = transverse_axis; break; case 1: // RL lx = -layer_axis; ly = transverse_axis; break; case 2: // TB lx = transverse_axis; ly = layer_axis; break; case 3: // BT lx = transverse_axis; ly = -layer_axis; break; default: lx = layer_axis; ly = transverse_axis; break; } n.x = lx; n.y = ly; n.vx = 0.0f; n.vy = 0.0f; }; // Centramos cada nivel en torno a 0 en el eje transversal y arrancamos en // 0 en el eje de capas. for (int L = 0; L <= max_level; ++L) { const auto& cur = by_level[L]; int sz = (int)cur.size(); if (sz == 0) continue; float layer_axis = (float)L * layer_spacing; float t0 = -0.5f * (sz - 1) * node_spacing; for (int j = 0; j < sz; ++j) { place(cur[j], layer_axis, t0 + j * node_spacing); } } g.update_bounds(); } // --------------------------------------------------------------------------- // layout_fixed — no-op. Existe para que el caller pueda usar "fixed" en un // switch sin casos especiales. // --------------------------------------------------------------------------- void layout_fixed(GraphData& /*g*/) { // intencionalmente vacio } } // namespace graph