7644a50d00
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>
46 lines
2.2 KiB
C
46 lines
2.2 KiB
C
#pragma once
|
|
|
|
struct GraphData; // forward declare
|
|
|
|
struct ForceLayoutConfig {
|
|
float repulsion = 500.0f; // repulsion strength between all nodes
|
|
float attraction = 0.01f; // spring constant for edges
|
|
float damping = 0.85f; // velocity decay per step
|
|
float min_distance = 1.0f; // minimum distance (avoid division by zero)
|
|
float theta = 0.5f; // Barnes-Hut threshold (0 = exact, 1 = fast)
|
|
float gravity = 0.1f; // pull toward center (prevents drift)
|
|
float max_velocity = 50.0f; // cap velocity per axis
|
|
int iterations = 1; // steps per call
|
|
};
|
|
|
|
// Perform one (or more) steps of force-directed layout.
|
|
// Modifies node positions (x, y) and velocities (vx, vy) in-place.
|
|
// Returns the total kinetic energy (sum of |v|^2). When energy < threshold,
|
|
// layout has converged.
|
|
float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config = {});
|
|
|
|
// Reset: randomize positions within [-spread, spread], zero velocities.
|
|
void graph_force_layout_reset(GraphData& graph, float spread = 200.0f);
|
|
|
|
// Preset layouts (non-iterative). Wrappers deprecados que delegan en
|
|
// graph_layouts.h. Se mantienen por compat — usar graph::layout_circular /
|
|
// graph::layout_grid directamente en codigo nuevo.
|
|
void graph_layout_circular(GraphData& graph, float radius = 100.0f);
|
|
void graph_layout_grid(GraphData& graph, float spacing = 20.0f);
|
|
|
|
// Auto-pause helper. Pure: el caller mantiene `consecutive_low_frames` y se
|
|
// encarga de incrementarlo / ponerlo a cero cada frame.
|
|
//
|
|
// Patron de uso tipico:
|
|
// static int low = 0;
|
|
// float energy = graph_force_layout_step(g, cfg);
|
|
// float per_node = g.node_count > 0 ? energy / g.node_count : 0.0f;
|
|
// if (per_node < threshold) low++; else low = 0;
|
|
// if (graph_force_layout_should_pause(low, min_consecutive)) running = false;
|
|
//
|
|
// Devuelve `true` si la energia ha caido por debajo del umbral durante al
|
|
// menos `min_consecutive` frames consecutivos. La firma toma `low_frames`
|
|
// directamente (en lugar de manejar el contador internamente) para que la
|
|
// funcion sea pura — facil de testear y sin estado oculto.
|
|
bool graph_force_layout_should_pause(int consecutive_low_frames, int min_consecutive);
|