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 ae47b76d0c
commit c29428a187
19 changed files with 756 additions and 177 deletions
+44 -16
View File
@@ -13,19 +13,24 @@
#include <algorithm>
// ---------------------------------------------------------------------------
// Community palette (ABGR packed, 10 colors)
// Fallback palette (RGBA8 con R en LSB) — usada solo cuando GraphData::types
// esta vacio. En el modelo extendido (issue 0049e) la apariencia de cada
// nodo viene resuelta por `resolve_node_color()`, que mira primero el
// override del nodo, luego el EntityType de la tabla, y finalmente este
// fallback. Mantener 10 colores para nodos sin tipos sigue siendo util en
// demos que aun no construyen tablas EntityType.
// ---------------------------------------------------------------------------
static const uint32_t k_palette[10] = {
0xFF4CAF50, // green
0xFFF44336, // red
0xFF2196F3, // blue
0xFFFF9800, // orange
0xFF9C27B0, // purple
0xFF00BCD4, // cyan
0xFFFFEB3B, // yellow
0xFFE91E63, // pink
0xFF795548, // brown
0xFF607D8B // blue-grey
static const uint32_t k_fallback_palette[10] = {
0xFF4CAF50u, // green
0xFFF44336u, // red
0xFF2196F3u, // blue
0xFFFF9800u, // orange
0xFF9C27B0u, // purple
0xFF00BCD4u, // cyan
0xFFFFEB3Bu, // yellow
0xFFE91E63u, // pink
0xFF795548u, // brown
0xFF607D8Bu, // blue-grey
};
// ---------------------------------------------------------------------------
@@ -562,8 +567,17 @@ unsigned int graph_renderer_draw(GraphRenderer* r, const GraphData& graph,
const GraphEdge& e = graph.edges[i];
if (e.source >= (uint32_t)graph.node_count) continue;
if (e.target >= (uint32_t)graph.node_count) continue;
uint32_t col = e.color != 0 ? e.color
: pack_rgba8(0x88, 0x88, 0x88, 0xFF);
if (!(e.flags & EF_VISIBLE)) continue;
// Saltamos aristas cuyos endpoints no estan visibles —
// el shader las pintaria igualmente (las posiciones siguen
// en el TBO) pero la aristas tendrian apariencia conectando
// "vacios" si los nodos estan ocultos por NF_VISIBLE off.
if (!(graph.nodes[e.source].flags & NF_VISIBLE)) continue;
if (!(graph.nodes[e.target].flags & NF_VISIBLE)) continue;
uint32_t col = resolve_edge_color(e, graph.rel_types,
graph.rel_type_count);
if (col == 0u) col = pack_rgba8(0x88, 0x88, 0x88, 0xFF);
r->edge_static_staging[out++] = { e.source, e.target, col, 0u };
}
if (out > 0) {
@@ -625,13 +639,27 @@ unsigned int graph_renderer_draw(GraphRenderer* r, const GraphData& graph,
size_t visible = 0;
for (int i = 0; i < graph.node_count; ++i) {
const GraphNode& n = graph.nodes[i];
float sz = n.size > 0.0f ? n.size : 4.0f;
if (!(n.flags & NF_VISIBLE)) continue;
float sz = resolve_node_size(n, graph.types, graph.type_count);
if (sz <= 0.0f) sz = 4.0f;
float half = sz * 0.5f;
// AABB del nodo: centro ± half. Skip si fuera del viewport.
if (n.x + half < vx0 || n.x - half > vx1) continue;
if (n.y + half < vy0 || n.y - half > vy1) continue;
uint32_t ncol = n.color != 0 ? n.color : k_palette[n.community % 10];
// Apariencia: 1) override del nodo, 2) EntityType, 3) fallback
// indexado por type_id (paleta de 10 — sustituye al community
// del modelo v1).
uint32_t ncol;
if (n.color_override != 0u) {
ncol = n.color_override;
} else if (graph.types && n.type_id < (uint16_t)graph.type_count) {
ncol = graph.types[n.type_id].color;
} else {
ncol = k_fallback_palette[n.type_id % 10];
}
// 0049f anadira el dispatch de shape; por ahora todos circulos.
r->node_staging[visible++] = { n.x, n.y, sz, ncol };
}