feat(viz): graph_layouts (radial/hierarchical/fixed) + viewport multi-select+lasso (issue 0049i)
Phase 1 — graph_layouts:
- New module cpp/functions/viz/graph_layouts.{h,cpp,md} v1.0.0
- layout_grid, layout_circular, layout_random (migrated from graph_force_layout.cpp)
- layout_radial: BFS rings from root, hop k -> circle of radius k*ring_spacing
- layout_hierarchical: Sugiyama-style heuristic (longest-path levels + barycenter ordering)
- layout_fixed: no-op
- All respect NF_PINNED. graph_layout_circular/grid kept as deprecated wrappers.
Phase 2-3 — graph_viewport v1.2.0:
- Multi-selection via state.selection (vector<int>); NF_SELECTED kept in sync
- Lasso: Shift+Drag on empty area; AABB hit-test on release
- Drag of N-selection: all selected pinned + moved by mouse delta
- Ctrl+click toggle, Esc clears selection
- Right-click on node -> on_context_menu callback
- Double-click on node -> on_double_click callback
- Helpers exposed: graph_viewport_clear/add_to/toggle/is_selected (own TU for tests)
Phase 4 — tests:
- test_graph_layouts: 12 cases / 364 assertions covering geometry, pin, edges
- test_graph_viewport: 5 cases for selection helpers (pure logic, no GL)
Phase 5 — demo (primitives_gallery):
- Layout combo (force/grid/circular/radial/hierarchical/fixed) + Apply button
- Right-click popup with Pin/Unpin/Add-to-selection
- Status overlay shows [N selected] when selection non-empty
- Updated golden images
Issue moved to dev/issues/completed/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "viz/graph_force_layout.h"
|
||||
#include "viz/graph_layouts.h"
|
||||
#include "viz/graph_types.h"
|
||||
|
||||
#include <cmath>
|
||||
@@ -342,19 +343,9 @@ void graph_force_layout_reset(GraphData& graph, float spread) {
|
||||
graph.update_bounds();
|
||||
}
|
||||
|
||||
// Wrappers deprecados — delegan en graph::layout_* (graph_layouts.h).
|
||||
void graph_layout_circular(GraphData& graph, float radius) {
|
||||
if (graph.node_count <= 0) return;
|
||||
const float two_pi = 6.28318530718f;
|
||||
for (int i = 0; i < graph.node_count; ++i) {
|
||||
GraphNode& n = graph.nodes[i];
|
||||
if (n.flags & NF_PINNED) continue;
|
||||
float angle = two_pi * (float)i / (float)graph.node_count;
|
||||
n.x = radius * std::cos(angle);
|
||||
n.y = radius * std::sin(angle);
|
||||
n.vx = 0.0f;
|
||||
n.vy = 0.0f;
|
||||
}
|
||||
graph.update_bounds();
|
||||
graph::layout_circular(graph, radius);
|
||||
}
|
||||
|
||||
bool graph_force_layout_should_pause(int consecutive_low_frames, int min_consecutive) {
|
||||
@@ -363,20 +354,5 @@ bool graph_force_layout_should_pause(int consecutive_low_frames, int min_consecu
|
||||
}
|
||||
|
||||
void graph_layout_grid(GraphData& graph, float spacing) {
|
||||
if (graph.node_count <= 0) return;
|
||||
int cols = (int)std::ceil(std::sqrt((float)graph.node_count));
|
||||
int rows = (graph.node_count + cols - 1) / cols;
|
||||
float ox = -0.5f * (cols - 1) * spacing;
|
||||
float oy = -0.5f * (rows - 1) * spacing;
|
||||
for (int i = 0; i < graph.node_count; ++i) {
|
||||
GraphNode& n = graph.nodes[i];
|
||||
if (n.flags & NF_PINNED) continue;
|
||||
int col = i % cols;
|
||||
int row = i / cols;
|
||||
n.x = ox + col * spacing;
|
||||
n.y = oy + row * spacing;
|
||||
n.vx = 0.0f;
|
||||
n.vy = 0.0f;
|
||||
}
|
||||
graph.update_bounds();
|
||||
graph::layout_grid(graph, spacing);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user