#include "demos.h" #include "demo.h" #include "viz/graph_types.h" #include "viz/graph_viewport.h" #include "viz/graph_renderer.h" #include "viz/graph_force_layout.h" #include "viz/graph_icons.h" #include "core/button.h" #include "core/tokens.h" #include #include #include #include namespace gallery { // 6 codepoints Tabler representativos para los 6 EntityTypes del demo. El // orden coincide con `s_entity_types[i]`: cada tipo apunta a `icon_id = i+1` // (las regiones del atlas son 1-indexed; 0 reservado para "sin icono"). static const uint16_t k_demo_codepoints[6] = { 0xEB4Du, // TI_USER 0xEAE5u, // TI_MAIL 0xEAB9u, // TI_GLOBE 0xEB09u, // TI_PHONE 0xEA4Fu, // TI_BUILDING 0xEA88u, // TI_DATABASE }; static const uint32_t k_styles_palette[6] = { 0xFF6BCB77u, // verde — Person (circle) 0xFFFF6B6Bu, // rojo — Email (square) 0xFF4D96FFu, // azul — Domain (diamond) 0xFFFFC75Fu, // ambar — Phone (hex) 0xFFC780E8u, // morado — Org (triangle) 0xFF52CDF2u, // cyan — Database (rounded square) }; static const char* k_styles_names[6] = { "Person", "Email", "Domain", "Phone", "Organization", "Database" }; static EntityType s_entity_types[6]; static RelationType s_relation_types[3]; // solid, dashed, dotted static IconAtlas* s_atlas = nullptr; static bool s_types_initialized = false; static bool s_atlas_bound = false; static void init_demo_types() { if (s_types_initialized) return; for (int i = 0; i < 6; ++i) { EntityType t{}; t.color = k_styles_palette[i]; t.shape = (uint8_t)(SHAPE_CIRCLE + i); // 1..6 — uno por shape t.icon_id = (uint16_t)(i + 1); // 1-based t.default_size = 14.0f; t.name = k_styles_names[i]; s_entity_types[i] = t; } s_relation_types[0] = relation_type(0xFFCCCCCCu, EDGE_SOLID, 1.5f, "knows"); s_relation_types[1] = relation_type(0xFFFFB870u, EDGE_DASHED, 1.5f, "uses"); s_relation_types[2] = relation_type(0xFF89E0FCu, EDGE_DOTTED, 1.5f, "owns"); s_types_initialized = true; } // 30 nodos posicionados en un anillo por tipo. Aristas: cada nodo conecta a // sus dos vecinos (arc) y a un nodo "central" del cluster siguiente. Mezcla // de directed/undirected para validar las flechas. static void build_demo_graph(std::vector& nodes, std::vector& edges) { nodes.clear(); edges.clear(); const int per_type = 5; const float ring_r = 80.0f; const float type_r = 30.0f; for (int t = 0; t < 6; ++t) { float ang_t = (float)t * (2.0f * 3.14159265f / 6.0f); float cx = std::cos(ang_t) * ring_r; float cy = std::sin(ang_t) * ring_r; for (int k = 0; k < per_type; ++k) { float a = (float)k * (2.0f * 3.14159265f / per_type) + ang_t * 0.3f; GraphNode n = graph_node(cx + std::cos(a) * type_r, cy + std::sin(a) * type_r, (uint16_t)t); n.user_data = (uint64_t)nodes.size(); nodes.push_back(n); } } auto idx = [&](int t, int k) { return (uint32_t)(t * per_type + k); }; for (int t = 0; t < 6; ++t) { // Aristas intra-cluster (knows = solid, undirected). for (int k = 0; k < per_type; ++k) { int next_k = (k + 1) % per_type; GraphEdge e = graph_edge(idx(t, k), idx(t, next_k), 1.0f, /*type_id=*/0); edges.push_back(e); } // Inter-cluster: del nodo 0 del cluster t al nodo 0 del cluster t+1 // como "uses" (dashed, directed). int t_next = (t + 1) % 6; GraphEdge e1 = graph_edge(idx(t, 0), idx(t_next, 0), 1.0f, /*type_id=*/1); e1.flags |= EF_DIRECTED; edges.push_back(e1); // Y otra inter-cluster mas larga al cluster +2 como "owns" (dotted, // directed). Asi se ven las 3 estilos a la vez. int t_far = (t + 2) % 6; GraphEdge e2 = graph_edge(idx(t, 2), idx(t_far, 3), 0.6f, /*type_id=*/2); e2.flags |= EF_DIRECTED; edges.push_back(e2); } } void demo_graph_styles() { demo_header("graph_renderer (shapes + icons + arrows + edge styles)", "v1.5.0", "OSINT-style: 6 EntityTypes, uno por shape (circle, square, diamond, hex, " "triangle, rounded square) con icono Tabler en el centro. 3 RelationTypes " "(solid/dashed/dotted) con flechas en los aristas EF_DIRECTED. Mismas dos " "draw calls que el viewport normal (1 nodos + 1 aristas)."); init_demo_types(); static std::vector s_nodes; static std::vector s_edges; static GraphData s_graph{}; static GraphViewportState s_state; static bool s_initialized = false; static bool s_run_layout = false; if (!s_initialized) { build_demo_graph(s_nodes, s_edges); s_graph.nodes = s_nodes.data(); s_graph.node_count = (int)s_nodes.size(); s_graph.node_capacity = (int)s_nodes.capacity(); s_graph.edges = s_edges.data(); s_graph.edge_count = (int)s_edges.size(); s_graph.edge_capacity = (int)s_edges.capacity(); s_graph.types = s_entity_types; s_graph.type_count = 6; s_graph.rel_types = s_relation_types; s_graph.rel_type_count = 3; s_graph.update_bounds(); s_state.layout_running = false; // queremos ver las shapes posicionadas, no el caos del force s_state.zoom = 2.0f; s_initialized = true; } section("Legend"); { ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted); for (int i = 0; i < 6; ++i) { ImGui::Text("%-13s shape=%d icon_id=%d color=#%06x", k_styles_names[i], (int)s_entity_types[i].shape, (int)s_entity_types[i].icon_id, (unsigned)(s_entity_types[i].color & 0x00FFFFFFu)); } ImGui::Text("Edges: knows=solid, uses=dashed (directed), owns=dotted (directed)"); ImGui::PopStyleColor(); } section("Controls"); { using namespace fn_ui; if (button(s_run_layout ? "Pause force layout" : "Run force layout", ButtonVariant::Secondary)) { s_run_layout = !s_run_layout; s_state.layout_running = s_run_layout; } ImGui::SameLine(); if (button("Rebuild", ButtonVariant::Subtle)) { build_demo_graph(s_nodes, s_edges); s_graph.nodes = s_nodes.data(); s_graph.node_count = (int)s_nodes.size(); s_graph.edges = s_edges.data(); s_graph.edge_count = (int)s_edges.size(); s_graph.update_bounds(); } ImGui::SameLine(); if (button("Fit view", ButtonVariant::Subtle)) { graph_viewport_fit(s_graph, s_state); } } section("Viewport"); if (s_run_layout) { ForceLayoutConfig cfg; cfg.repulsion = 1500.0f; cfg.attraction = 0.04f; cfg.gravity = 0.005f; cfg.iterations = 1; graph_force_layout_step(s_graph, cfg); } // El viewport crea internamente el GraphRenderer. La primera vez que se // dibuja el panel, el renderer existe — bindeamos el atlas justo despues. graph_viewport("##graph_styles", s_graph, s_state, ImVec2(0, 460)); if (!s_atlas_bound && s_state.renderer) { s_atlas = graph_icons_build(k_demo_codepoints, 6, 32); if (s_atlas) { graph_renderer_set_icon_atlas(s_state.renderer, graph_icons_texture(s_atlas), graph_icons_uv_table(s_atlas), graph_icons_count(s_atlas)); s_atlas_bound = true; } else { // Sin atlas: marcamos como bound para no reintentar cada frame — // el renderer simplemente pinta sin overlay de iconos. s_atlas_bound = true; } } code_block( "// Build atlas con 6 codepoints Tabler\n" "const uint16_t cps[] = {0xEB4D, 0xEAE5, 0xEAB9, 0xEB09, 0xEA4F, 0xEA88};\n" "IconAtlas* atlas = graph_icons_build(cps, 6, 32);\n" "\n" "// EntityTypes: cada uno con su shape e icono\n" "EntityType person = {0xFF6BCB77, SHAPE_CIRCLE, /*icon_id=*/1, 14, \"Person\"};\n" "EntityType email = {0xFFFF6B6B, SHAPE_SQUARE, /*icon_id=*/2, 14, \"Email\"};\n" "// ... etc\n" "\n" "// RelationTypes: solid / dashed / dotted\n" "RelationType knows = relation_type(0xFFCCCCCC, EDGE_SOLID, 1.5f, \"knows\");\n" "RelationType uses = relation_type(0xFFFFB870, EDGE_DASHED, 1.5f, \"uses\");\n" "\n" "// Bind atlas al renderer\n" "graph_renderer_set_icon_atlas(renderer, graph_icons_texture(atlas),\n" " graph_icons_uv_table(atlas),\n" " graph_icons_count(atlas));\n" "\n" "// Aristas direccionales\n" "GraphEdge e = graph_edge(src, tgt, 1.0f, /*type_id=*/1);\n" "e.flags |= EF_DIRECTED;"); } } // namespace gallery