Files
fn_registry/cpp/functions/viz/graph_labels.md
T
egutierrez 4b28ef6774 feat(viz): graph_labels render con LabelPolicy + ImDrawList (issue 0049j)
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.
2026-04-29 23:53:32 +02:00

7.6 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_labels function cpp viz 1.0.0 impure 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) 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.
graph
labels
imdrawlist
viewport
osint
culling
top-n
GraphData_cpp_viz
false error_go_core
imgui.h
true
select respects max_visible cap
min_node_pixel_size culling
always_for_selected gates by viewport
no callback / empty graph no-op
cpp/tests/test_graph_labels.cpp cpp/functions/viz/graph_labels.cpp imgui
name desc
graph Grafo a etiquetar (lectura). Se respetan NF_VISIBLE, NF_SELECTED, NF_HOVERED, NF_PINNED.
name desc
state GraphViewportState con camara y zoom. graph_labels_draw lo usa para world->screen.
name desc
cam_x (draw_at/select) Centro de la camara en world coords (X).
name desc
cam_y (draw_at/select) Centro de la camara en world coords (Y).
name desc
zoom (draw_at/select) Pixels por unidad world. zoom <= 0 → no-op.
name desc
wmin_x (draw_at) Esquina superior-izquierda del widget en pixeles de pantalla (X).
name desc
wmin_y (draw_at) Esquina superior-izquierda del widget en pixeles de pantalla (Y).
name desc
w (draw_at/select) Ancho del widget en pixeles.
name desc
h (draw_at/select) Alto del widget en pixeles.
name desc
policy LabelPolicy: max_visible (top-N), always_for_*, min_zoom_for_all, min_node_pixel_size, font_size, color, bg_color, padding.
name desc
cb Callback `const char* (int node_idx, void* user)` que devuelve el texto del label. NULL/'' omite el nodo.
name desc
user Puntero opaco que se pasa al callback. Util para pasar el string pool o metadata del consumer.
name desc
degrees (select) Array de tamano node_count con el grado por nodo. NULL → score = size sin factor degree.
name desc
out_indices (select) Array de salida con los indices de nodos a etiquetar. Capacidad = out_capacity.
name desc
out_capacity (select) Capacidad maxima de out_indices. La funcion no escribe mas alla.
name desc
out_degrees (compute_degrees) Array de tamano node_count que recibe el grado por nodo. Cuenta solo aristas con EF_VISIBLE.
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. 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

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)

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.