4b28ef6774
graph_labels_draw pinta etiquetas de nodos sobre el FBO del graph_renderer via ImDrawList. Politica configurable: always-on para selected/hovered/ pinned, top-N por size*(degree+1), culling por viewport AABB y min_node_pixel_size. Cap duro = max_visible + |always_*|. API: - graph_labels_draw(graph, viewport_state, policy, cb, user) - graph_labels_draw_at(...) — variante con rect explicito - graph_labels_select(...) — helper puro testeable - graph_compute_degrees(...) — O(E) Splitting en dos TUs: - graph_labels.cpp — funciones draw (depende de ImGui) - graph_labels_select.cpp — helpers puros para tests sin ImGui 12 tests en test_graph_labels (culling, max_visible cap, min_pixel_size, always_* gating por viewport, top-N por score, edge cases). Todos verdes. Integrado en demos_graph con UI: toggle Labels, sliders Max visible / Font / Min px, checkboxes Selected/Hovered/Pinned. Golden de graph_viewport regenerado. Cierra issue 0049j.
155 lines
7.6 KiB
Markdown
155 lines
7.6 KiB
Markdown
---
|
|
name: graph_labels
|
|
kind: function
|
|
lang: cpp
|
|
domain: viz
|
|
version: "1.0.0"
|
|
purity: impure
|
|
signature: "void graph::graph_labels_draw(const GraphData&, const GraphViewportState&, const LabelPolicy&, GetLabelFn, void*); void graph::graph_labels_draw_at(const GraphData&, float cam_x, float cam_y, float zoom, float wmin_x, float wmin_y, float w, float h, const LabelPolicy&, GetLabelFn, void*); int graph::graph_labels_select(const GraphData&, const LabelPolicy&, float cam_x, float cam_y, float zoom, float w, float h, const int* degrees, int* out_indices, int out_capacity); void graph::graph_compute_degrees(const GraphData&, int* out_degrees)"
|
|
description: "Renderiza etiquetas de nodos sobre el viewport del grafo via ImDrawList con politica configurable: always-on para selected/hovered/pinned, top-N por (size * (degree+1)), y culling por viewport + min pixel size. Independiente del renderer GPU."
|
|
tags: [graph, labels, imdrawlist, viewport, osint, culling, top-n]
|
|
uses_functions: []
|
|
uses_types: ["GraphData_cpp_viz"]
|
|
returns: []
|
|
returns_optional: false
|
|
error_type: "error_go_core"
|
|
imports: ["imgui.h"]
|
|
tested: true
|
|
tests: ["select respects max_visible cap", "min_node_pixel_size culling", "always_for_selected gates by viewport", "no callback / empty graph no-op"]
|
|
test_file_path: "cpp/tests/test_graph_labels.cpp"
|
|
file_path: "cpp/functions/viz/graph_labels.cpp"
|
|
framework: imgui
|
|
params:
|
|
- name: graph
|
|
desc: "Grafo a etiquetar (lectura). Se respetan NF_VISIBLE, NF_SELECTED, NF_HOVERED, NF_PINNED."
|
|
- name: state
|
|
desc: "GraphViewportState con camara y zoom. graph_labels_draw lo usa para world->screen."
|
|
- name: cam_x
|
|
desc: "(draw_at/select) Centro de la camara en world coords (X)."
|
|
- name: cam_y
|
|
desc: "(draw_at/select) Centro de la camara en world coords (Y)."
|
|
- name: zoom
|
|
desc: "(draw_at/select) Pixels por unidad world. zoom <= 0 → no-op."
|
|
- name: wmin_x
|
|
desc: "(draw_at) Esquina superior-izquierda del widget en pixeles de pantalla (X)."
|
|
- name: wmin_y
|
|
desc: "(draw_at) Esquina superior-izquierda del widget en pixeles de pantalla (Y)."
|
|
- name: w
|
|
desc: "(draw_at/select) Ancho del widget en pixeles."
|
|
- name: h
|
|
desc: "(draw_at/select) Alto del widget en pixeles."
|
|
- name: policy
|
|
desc: "LabelPolicy: max_visible (top-N), always_for_*, min_zoom_for_all, min_node_pixel_size, font_size, color, bg_color, padding."
|
|
- name: cb
|
|
desc: "Callback `const char* (int node_idx, void* user)` que devuelve el texto del label. NULL/'' omite el nodo."
|
|
- name: user
|
|
desc: "Puntero opaco que se pasa al callback. Util para pasar el string pool o metadata del consumer."
|
|
- name: degrees
|
|
desc: "(select) Array de tamano node_count con el grado por nodo. NULL → score = size sin factor degree."
|
|
- name: out_indices
|
|
desc: "(select) Array de salida con los indices de nodos a etiquetar. Capacidad = out_capacity."
|
|
- name: out_capacity
|
|
desc: "(select) Capacidad maxima de out_indices. La funcion no escribe mas alla."
|
|
- name: out_degrees
|
|
desc: "(compute_degrees) Array de tamano node_count que recibe el grado por nodo. Cuenta solo aristas con EF_VISIBLE."
|
|
output: "Ninguno (void) salvo graph_labels_select que devuelve int (numero de indices escritos en out_indices). Las funciones draw pintan rectangulos y texto via ImDrawList del current ImGui window."
|
|
notes: "Issue 0049j. La estrategia de seleccion es: (A) nodos always_* en viewport sin chequeo de pixel size; (B) resto de nodos visibles que cumplen min_node_pixel_size, capados al top-N por (size * (degree+1)). Off-screen always_* se OMITEN intencionadamente para no spamear. El degree se calcula on-the-fly cada frame en draw_at (O(E)); para hot paths el caller puede precalcular y pasarlo a graph_labels_select. Cap duro = max_visible + |always_*|."
|
|
---
|
|
|
|
# graph_labels
|
|
|
|
Pintar etiquetas de nodos sobre la imagen del FBO del `graph_renderer`. Pensado
|
|
para uso OSINT/Maltego: necesitas leer "juan@x.com", IBAN, etc. en los nodos
|
|
relevantes, pero no puedes mostrar 20k labels — el ojo y la GPU se saturan.
|
|
|
|
## Estrategia
|
|
|
|
Cada frame el algoritmo decide que nodos etiquetar:
|
|
|
|
1. **Always-on** — si `LabelPolicy.always_for_{selected,hovered,pinned}` esta
|
|
activo y el nodo lleva ese flag, se etiqueta. Skip de `min_node_pixel_size`,
|
|
pero sigue requiriendo estar dentro del viewport (decision: off-screen no se
|
|
dibuja para no spamear con flechas o etiquetas en los bordes).
|
|
2. **Top-N** — del resto de nodos visibles en el viewport que ademas tienen
|
|
`size * zoom >= min_node_pixel_size`, se ordena por `score = size * (degree+1)`
|
|
y se cogen los `max_visible` primeros.
|
|
3. **Cap duro** — total = `max_visible + |always_*|`. Sin opciones de saltarse
|
|
esto. Si el caller quiere 0 labels excepto los always, basta poner
|
|
`max_visible = 0`.
|
|
|
|
`min_zoom_for_all` esta en la struct por compatibilidad con el spec original;
|
|
en la implementacion v1 actua como un parametro suave: con zoom alto la mayoria
|
|
de nodos visibles superan `min_node_pixel_size` y entran al top-N por si solos,
|
|
asi que el efecto deseado (mostrar todos a zoom alto) sale naturalmente sin
|
|
ramas adicionales.
|
|
|
|
## API
|
|
|
|
```cpp
|
|
namespace graph {
|
|
struct LabelPolicy { /* ... */ };
|
|
typedef const char* (*GetLabelFn)(int node_idx, void* user);
|
|
|
|
// Tras ImGui::Image(...) o graph_viewport(...). Resuelve rect via
|
|
// GetItemRectMin/Max y usa el ImDrawList del current window.
|
|
void graph_labels_draw (const GraphData&, const GraphViewportState&,
|
|
const LabelPolicy&, GetLabelFn, void*);
|
|
|
|
// Variante explicita: el caller pasa cam, zoom y rect del widget.
|
|
void graph_labels_draw_at(const GraphData&,
|
|
float cam_x, float cam_y, float zoom,
|
|
float wmin_x, float wmin_y,
|
|
float w, float h,
|
|
const LabelPolicy&, GetLabelFn, void*);
|
|
|
|
// Helpers puros (testables sin ImGui).
|
|
void graph_compute_degrees(const GraphData&, int* out_degrees);
|
|
int graph_labels_select (const GraphData&, const LabelPolicy&,
|
|
float cam_x, float cam_y, float zoom,
|
|
float w, float h,
|
|
const int* degrees,
|
|
int* out_indices, int out_capacity);
|
|
}
|
|
```
|
|
|
|
## Uso tipico (en demos_graph)
|
|
|
|
```cpp
|
|
graph_viewport("##g", graph, state, ImVec2(0, 460));
|
|
|
|
graph::LabelPolicy policy;
|
|
policy.max_visible = 200;
|
|
policy.always_for_selected = true;
|
|
policy.always_for_hovered = true;
|
|
policy.min_node_pixel_size = 12.0f;
|
|
|
|
struct LabelCtx { char buf[32]; };
|
|
auto get_label = [](int idx, void* user) -> const char* {
|
|
auto* ctx = (LabelCtx*)user;
|
|
std::snprintf(ctx->buf, sizeof(ctx->buf), "#%d", idx);
|
|
return ctx->buf;
|
|
};
|
|
LabelCtx ctx;
|
|
graph::graph_labels_draw(graph, state, policy, get_label, &ctx);
|
|
```
|
|
|
|
## Coste
|
|
|
|
- `graph_compute_degrees`: O(E).
|
|
- `graph_labels_select`: O(N) para frustum cull + O(K log K) si K candidatos
|
|
superan `max_visible` (partial_sort).
|
|
- Por label: 1 `AddRectFilled` + 1 `AddText`. ImDrawList es eficiente; con
|
|
`max_visible = 200` apenas se nota.
|
|
|
|
## Notas de integracion
|
|
|
|
- Llamar **despues** de `graph_viewport` (o `ImGui::Image` del FBO). El widget
|
|
rect se resuelve con `ImGui::GetItemRectMin/Max()`.
|
|
- Las labels se dibujan en el `ImDrawList` del **current window**. Si el FBO
|
|
esta dentro de un `BeginChild/EndChild`, el clip rect del child puede
|
|
recortar etiquetas de nodos pegados a los bordes — usar `draw_at` y pasar
|
|
el rect que se quiera respetar.
|
|
- El callback se invoca como mucho una vez por nodo etiquetado por frame.
|
|
- Para reproducibilidad en tests, usar `graph_labels_select` con un buffer
|
|
de capacidad conocida — no toca ImGui.
|