Files
fn_registry/cpp/functions/viz/graph_layouts.md
T
egutierrez 4a0750445c feat(viz): graph_layouts (radial/hierarchical/fixed) + viewport multi-select+lasso (issue 0049i)
Phase 1 — graph_layouts:
- New module cpp/functions/viz/graph_layouts.{h,cpp,md} v1.0.0
- layout_grid, layout_circular, layout_random (migrated from graph_force_layout.cpp)
- layout_radial: BFS rings from root, hop k -> circle of radius k*ring_spacing
- layout_hierarchical: Sugiyama-style heuristic (longest-path levels + barycenter ordering)
- layout_fixed: no-op
- All respect NF_PINNED. graph_layout_circular/grid kept as deprecated wrappers.

Phase 2-3 — graph_viewport v1.2.0:
- Multi-selection via state.selection (vector<int>); NF_SELECTED kept in sync
- Lasso: Shift+Drag on empty area; AABB hit-test on release
- Drag of N-selection: all selected pinned + moved by mouse delta
- Ctrl+click toggle, Esc clears selection
- Right-click on node -> on_context_menu callback
- Double-click on node -> on_double_click callback
- Helpers exposed: graph_viewport_clear/add_to/toggle/is_selected (own TU for tests)

Phase 4 — tests:
- test_graph_layouts: 12 cases / 364 assertions covering geometry, pin, edges
- test_graph_viewport: 5 cases for selection helpers (pure logic, no GL)

Phase 5 — demo (primitives_gallery):
- Layout combo (force/grid/circular/radial/hierarchical/fixed) + Apply button
- Right-click popup with Pin/Unpin/Add-to-selection
- Status overlay shows [N selected] when selection non-empty
- Updated golden images

Issue moved to dev/issues/completed/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:42:31 +02:00

5.5 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, framework, params, output, notes
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path framework params output notes
graph_layouts function cpp viz 1.0.0 pure void graph::layout_grid(GraphData&, float spacing); void graph::layout_circular(GraphData&, float radius); void graph::layout_random(GraphData&, float spread); void graph::layout_radial(GraphData&, int root_node, float ring_spacing); void graph::layout_hierarchical(GraphData&, int direction, float layer_spacing, float node_spacing); void graph::layout_fixed(GraphData&) Conjunto de layouts estaticos (no iterativos) para GraphData: grid, circular, random, radial, hierarchical (Sugiyama heuristico), fixed. Todos respetan NF_PINNED.
graph
layout
static
radial
hierarchical
sugiyama
osint
GraphData_cpp_viz
false
true
grid centers and respects pin
circular places on circle
radial root at center, hop ring
hierarchical levels by longest path
fixed is no-op
cpp/tests/test_graph_layouts.cpp cpp/functions/viz/graph_layouts.cpp imgui
name desc
graph Referencia al grafo (GraphData) cuyos nodos se reposicionan in-place. Mutaciones: x, y, vx=0, vy=0 por nodo no pinned.
name desc
spacing (layout_grid) Distancia en world units entre celdas adyacentes de la cuadricula.
name desc
radius (layout_circular) Radio del circulo unico en world units.
name desc
spread (layout_random) Mitad del lado del cuadrado en el que se distribuyen aleatoriamente las posiciones (rango [-spread, spread]).
name desc
root_node (layout_radial) Indice del nodo raiz. BFS desde aqui asigna el hop. Si es invalido, usa 0.
name desc
ring_spacing (layout_radial) Distancia radial entre anillos de hops consecutivos.
name desc
direction (layout_hierarchical) Orientacion: 0=LR (left->right), 1=RL, 2=TB (top->bottom), 3=BT.
name desc
layer_spacing (layout_hierarchical) Distancia entre niveles consecutivos en el eje del layout.
name desc
node_spacing (layout_hierarchical) Distancia entre nodos del mismo nivel en el eje transversal.
Ninguno (void). Los nodos no pinned se mueven in-place; nodos con NF_PINNED conservan posicion y velocidad. Issue 0049i. layout_circular/layout_grid migrados desde graph_force_layout.cpp; los wrappers deprecados graph_layout_circular/graph_layout_grid siguen funcionando como compat hasta cierre de 0049.

graph_layouts

Funciones de layout estatico (instantaneo, sin iterar). Todas escriben las posiciones de los nodos no pinned y cero las velocidades; los nodos con NF_PINNED no se tocan. Util para inicializar un grafo antes del force layout o para ofrecer alternativas de visualizacion en TUIs OSINT.

API

namespace graph {
    void layout_grid       (GraphData&, float spacing = 20.0f);
    void layout_circular   (GraphData&, float radius  = 100.0f);
    void layout_random     (GraphData&, float spread  = 200.0f);
    void layout_radial     (GraphData&, int root_node = 0,
                                       float ring_spacing = 80.0f);
    void layout_hierarchical(GraphData&, int direction = 0,
                                        float layer_spacing = 120.0f,
                                        float node_spacing  = 60.0f);
    void layout_fixed      (GraphData&);  // no-op
}

Algoritmos

layout_grid

Cuadricula uniforme. cols = ceil(sqrt(N)), rows = ceil(N/cols). Centro del grafo en (0,0).

layout_circular

Circulo unico. Cada nodo en el angulo 2*pi*i/N.

layout_random

Posiciones uniformes en [-spread, spread]^2. Usa rand() — llamar srand(seed) antes para reproducibilidad.

layout_radial

BFS no dirigido desde root_node. Cada hop k se coloca en un circulo de radio k * ring_spacing. Nodos del mismo hop se distribuyen uniformemente en su circulo. Las componentes desconectadas van a un anillo extra al final. Util para vistas de "vecinos a N saltos" tipicas en OSINT.

layout_hierarchical

Sugiyama-style heuristico:

  1. Niveles por longest-path BFS desde nodos sin in-edges.
  2. Reduccion de cruces greedy: cada nivel L>0 se reordena por el baricentro de los indices de sus padres en el nivel L-1.
  3. Posiciones: el nivel define el eje principal (X o Y segun direction), los nodos dentro del nivel se centran en el eje transversal.

No es Sugiyama optimo (eso es un problema NP-hard). Es suficientemente bueno para grafos OSINT pequenios/medianos: jerarquias Person -> Email -> Domain, arboles de dependencia, etc.

layout_fixed

No-op. Se mantiene en el conjunto para que un caller pueda escribir un switch que cubra todos los modos sin un caso especial.

Composicion con graph_force_layout

Tipico: layout estatico inicial -> force-directed para refinar.

graph::layout_circular(g, 200.0f);   // arranque uniforme
ForceLayoutConfig cfg;
for (int i = 0; i < 300; ++i) {
    graph_force_layout_step(g, cfg);
}

Para grafos jerarquicos donde no se quiere que el force layout deshaga la estructura: pinnar nodos clave (NF_PINNED) tras layout_hierarchical y dejar que el force solo refine los del medio.

Notas

  • Wrappers deprecados: graph_layout_circular y graph_layout_grid siguen en graph_force_layout.h y delegan aqui. Codigo nuevo debe usar el namespace graph::.
  • layout_radial y layout_hierarchical construyen una lista de adyacencia temporal O(V+E) por llamada. Para grafos enormes (>>1M aristas) considerar cachear la adyacencia entre llamadas.