feat(viz): graph_types modelo extendido + EntityType/RelationType + flags (issue 0049e)

Extiende el modelo agnostico de graph_types.h para soportar shapes/iconos/
filtros/labels/streaming sin acoplar a backend. Migra el unico consumer
(demos_graph) en el mismo cambio.

- GraphNode v2: type_id + shape_override/color_override/size_override +
  flags (NF_PINNED/VISIBLE/SELECTED/HOVERED) + label_idx + user_data.
- GraphEdge v2: type_id + style_override + flags (EF_DIRECTED/VISIBLE).
- EntityType / RelationType: tablas en GraphData (types, rel_types).
- Helpers de resolucion (resolve_node_color/shape/size, resolve_edge_*)
  y constructores ergonomicos (graph_node, graph_edge, entity_type,
  relation_type) — sentinel-based para herencia automatica del tipo.
- graph_renderer v1.4: lee NF_VISIBLE / EF_VISIBLE, resuelve apariencia
  via override → EntityType → fallback indexado por type_id. Skipea
  aristas con endpoints invisibles. Shapes siguen pintandose como
  circulo (0049f cableara el dispatch real).
- graph_force_layout v1.2: pinned ahora vive en flags & NF_PINNED.
- graph_viewport v1.1: hover/seleccion publican NF_HOVERED/SELECTED en
  el grafo (clear-then-set). Drag usa NF_PINNED. Tooltip muestra Type/
  user_data en lugar de community/value/label.
- demos_graph: 8 EntityType (paleta antigua) + 1 RelationType. type_id
  por cluster. user_data = indice numerico del nodo. Apariencia visual
  identica al pre-cambio.
- test_graph_types.cpp: 12 casos cubriendo helpers, defaults, bitmask
  manipulation y resoluciones override-vs-EntityType. test_graph_edge_
  static actualizado al nuevo modelo (ya no tiene .color directo).
- 4 .md de tipos nuevos (graph_node, graph_edge, entity_type,
  relation_type) + GraphData v2.0 actualizado.

Tests: 31/31 ctest verdes (incluye test_visual golden).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 22:44:40 +02:00
parent a6e3298f1b
commit b9ffc13caf
19 changed files with 756 additions and 177 deletions
@@ -0,0 +1,187 @@
# 0049e — Modelo de datos extendido: `GraphNode`/`GraphEdge` + `EntityType`/`RelationType`
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0049e |
| **Estado** | pendiente |
| **Prioridad** | alta |
| **Tipo** | breaking change controlado — parte de [#0049](0049-osint-graph-viewer.md) |
## Dependencias
**Bloqueada por:** [0049d](0049d-graph-edges-vertex-pulling.md) (cambios pegan en mismo `graph_types.h`; mejor encadenarlos).
**Desbloquea:** [0049f](0049f-graph-renderer-symbols.md), [0049g](0049g-graph-source-operations.md), [0049i](0049i-graph-layouts-static.md).
---
## Objetivo
Extender `graph_types.h` con el modelo agnostico necesario para shapes/iconos/filtros/labels/streaming, sin acoplar a ningun backend de datos. Migrar `demos_graph.cpp` (unico consumer actual) en el mismo sub-issue.
## Contexto
El modelo actual es minimo (`x, y, vx, vy, size, color, community`). Para soportar:
- Shapes y iconos per-tipo
- Filtrado por tipo y per-nodo
- Selection / hover / pin
- Mapeo de vuelta a la entidad real del backend
- Aristas con tipo, direccion, estilo
Se necesita `type_id`, `flags`, `*_override`, `label_idx`, `user_data` en nodos; `type_id`, `flags`, `style_override` en aristas; tablas `EntityType[]` / `RelationType[]`.
## Arquitectura
```
cpp/functions/viz/
├── graph_types.h # MOD: structs extendidos + enums
├── graph_types.cpp # MOD: helpers (graph_node, graph_edge actualizados)
├── graph_types.md # MOD: bump
├── graph_renderer.{h,cpp} # MOD: leer type_id, flags, overrides al pintar
├── graph_force_layout.{h,cpp} # MOD: respetar flags.pinned
├── graph_viewport.{h,cpp} # MOD: setear flags.selected/hovered
cpp/apps/primitives_gallery/
└── demos_graph.cpp # MOD: migrar al modelo nuevo
cpp/tests/
└── test_graph_types.cpp # NEW
```
### Modelo final
```cpp
// graph_types.h
namespace graph {
enum NodeFlags : uint8_t {
NF_NONE = 0,
NF_PINNED = 1 << 0,
NF_VISIBLE = 1 << 1,
NF_SELECTED = 1 << 2,
NF_HOVERED = 1 << 3,
};
enum EdgeFlags : uint8_t {
EF_NONE = 0,
EF_DIRECTED = 1 << 0,
EF_VISIBLE = 1 << 1,
};
enum Shape : uint8_t {
SHAPE_CIRCLE = 0, SHAPE_SQUARE, SHAPE_DIAMOND, SHAPE_HEX,
SHAPE_TRIANGLE, SHAPE_ROUNDED_SQUARE,
};
enum EdgeStyle : uint8_t {
EDGE_SOLID = 0, EDGE_DASHED, EDGE_DOTTED,
};
struct GraphNode {
float x, y, vx, vy;
uint16_t type_id;
uint8_t shape_override; // 0 = use type, otherwise SHAPE_*
uint8_t flags; // NF_* mask, default NF_VISIBLE
uint32_t color_override; // 0 = use type, !=0 = RGBA8
float size_override; // 0 = use type
uint32_t label_idx; // index into consumer's string pool
uint64_t user_data; // opaque, app uses to map back to its DB
};
struct GraphEdge {
uint32_t source, target;
uint16_t type_id;
uint8_t style_override; // 0 = use type, otherwise EDGE_*
uint8_t flags; // EF_* mask
float weight;
uint32_t label_idx;
};
struct EntityType {
uint32_t color; // RGBA8
uint8_t shape; // SHAPE_*
uint16_t icon_id; // 0 = no icon
float default_size;
const char* name; // for UI/debug, not for render
};
struct RelationType {
uint32_t color;
uint8_t style; // EDGE_*
float width;
const char* name;
};
struct GraphData {
GraphNode* nodes; int node_count; int node_capacity;
GraphEdge* edges; int edge_count; int edge_capacity;
EntityType* types; int type_count;
RelationType* rel_types; int rel_type_count;
// bounds, etc. (existentes)
};
} // namespace graph
```
## Tareas
### Fase 1 — Tipos
- [ ] **1.1** Reescribir `graph_types.h` con el modelo de arriba.
- [ ] **1.2** Helpers `graph_node(...)` / `graph_edge(...)` para construccion ergonomica con defaults.
- [ ] **1.3** Crear `types/viz/graph_node.md`, `graph_edge.md`, `entity_type.md`, `relation_type.md` con frontmatter completo (algebraic: product). Si ya existen, bump version.
### Fase 2 — Renderer
- [ ] **2.1** En `graph_renderer.cpp`, resolver visual final del nodo:
```cpp
uint32_t color = n.color_override ? n.color_override : graph.types[n.type_id].color;
uint8_t shape = n.shape_override ? n.shape_override : graph.types[n.type_id].shape;
float size = n.size_override > 0 ? n.size_override : graph.types[n.type_id].default_size;
if (!(n.flags & NF_VISIBLE)) skip;
```
- [ ] **2.2** Idem para aristas: estilo y color del `RelationType`. Skip si `!(EF_VISIBLE)` o si los dos endpoints no son visibles.
- [ ] **2.3** Mientras tanto, todavia NO renderizamos shapes (eso es 0049f) — todos como circulo. Pero el dispatch ya esta cableado.
### Fase 3 — Force layout
- [ ] **3.1** En `graph_force_layout.cpp`, sustituir `n.pinned` por `(n.flags & NF_PINNED)`.
### Fase 4 — Viewport
- [ ] **4.1** En `graph_viewport.cpp`, setear `flags |= NF_HOVERED` y `flags |= NF_SELECTED` en lugar de los campos actuales.
- [ ] **4.2** Limpiar el flag al cambiar el target (clear-then-set).
### Fase 5 — Migrar `demos_graph`
- [ ] **5.1** Crear un `EntityType` y un `RelationType` por defecto (paleta antigua → 8 entity types, 1 relation type).
- [ ] **5.2** Asignar `type_id` por cluster como antes.
- [ ] **5.3** Setear `flags = NF_VISIBLE` en cada nodo creado y `EF_VISIBLE` en cada arista.
- [ ] **5.4** Ejecutar y verificar visual identico al pre-cambio.
### Fase 6 — Tests
- [ ] **6.1** `cpp/tests/test_graph_types.cpp`: helpers, defaults, flag manipulation, lookup color/shape con/sin override.
- [ ] **6.2** Visual golden de `demos_graph` regenerado si pixel-diff > tolerancia (no deberia).
### Fase 7 — Cleanup
- [ ] Bump version `graph_types`, `graph_renderer`, `graph_force_layout`, `graph_viewport`.
- [ ] `fn index`.
- [ ] Commit `feat(viz): graph_types modelo extendido + EntityType/RelationType + flags`.
## Criterio de done
- [ ] `demos_graph` visualmente identico al pre-cambio.
- [ ] Tests Catch2 verdes.
- [ ] Visual golden ok.
- [ ] `fn show graph_node_cpp_viz` (y los otros tipos) reflejan el nuevo schema.
## Riesgos
| Riesgo | Mitigacion |
|---|---|
| Tamaño del struct sube | `GraphNode` ~40 bytes y `GraphEdge` ~24 bytes — aceptable para 100k entidades (~4 MB) |
| Migrar `demos_graph` rompe golden | Regenerar golden si la diff es solo cosmetica; investigar si es semantica |
| Otros consumers ocultos | Solo `demos_graph` consume hoy — verificado con grep antes de mergear |