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