RelationshipLayout.h header
#include <ew/app/RelationshipLayout.h>
Namespace ew::app
ForceLayoutParams struct
struct ew::app::ForceLayoutParams
Tunables for forceDirectedLayout: the number of relaxation passes and the layout area. The defaults suit a graph of up to a few hundred nodes.
Members
int ew::app::relationship::ForceLayoutParams::iterations = 300
Relaxation passes for a graph of up to a few hundred nodes. Fixed (not until-convergence) so the result is deterministic. A larger graph runs proportionally fewer – see forceDirectedLayout, which explains why and what it costs.
double ew::app::relationship::ForceLayoutParams::width = 1200.0
Width of the layout area (scene units); node positions stay within [0, width].
double ew::app::relationship::ForceLayoutParams::height = 900.0
Height of the layout area (scene units); node positions stay within [0, height].
RadialLayoutParams struct
struct ew::app::RadialLayoutParams
Tunables for radialLayout: the centre the focus sits on and the gap between depth rings.
Members
double ew::app::relationship::RadialLayoutParams::centreX = 600.0
Scene x of the centre (where the focus node is placed).
double ew::app::relationship::RadialLayoutParams::centreY = 450.0
Scene y of the centre.
double ew::app::relationship::RadialLayoutParams::ringSpacing = 160.0
Distance between successive depth rings; a node at depth d sits d*ringSpacing from the centre.
Functions
std::map< ew::core::foundation::ContentId, QPointF > ew::app::relationship::forceDirectedLayout(const RelationshipGraph &graph, const ForceLayoutParams ¶ms={})
Computes a force-directed (Fruchterman-Reingold) layout for graph: nodes repel one another while edges pull their endpoints together, relaxed over params.iterations passes with a cooling step size, so an arbitrary graph spreads out legibly instead of piling onto a single circle – the new default general layout. Deterministic: nodes start on an index-seeded spiral and no randomness is used, so the same graph (in the same node order) lays out identically every run, which keeps it unit-testable and stable between openings. Disconnected components drift apart under mutual repulsion. Returns a position (within the params area) for every node, keyed by id; an empty map for an empty graph.
Cost. Every pass compares every pair, so the work is iterations * nodes^2. Measured on the release build at the full 300 passes: 155 nodes 29 ms, 600 nodes 363 ms, 1,000 nodes 943 ms, 2,000 nodes 3.6 seconds – and a caller that lays out on the GUI thread freezes for that long every time it rebuilds. Past a few hundred nodes the pass count is therefore reduced with the square of the graph size (never below a floor), which keeps a large campaign codex responsive; the reduction depends only on the node count, so the layout stays deterministic. A caller rebuilding on every interaction should still expect this to cost real time on a large graph.
std::map< ew::core::foundation::ContentId, QPointF > ew::app::relationship::hierarchicalLayout(const RelationshipGraph &graph)
Computes a top-down hierarchical (layered) layout for graph: each node is placed on a row given by the longest directed path of relationships leading into it, so roots sit at the top and their descendants cascade below – the shape a family tree or faction hierarchy needs. Nodes on the same row are spread evenly and centred. Nodes only reachable through a cycle fall to the top row. Returns a position (in scene coordinates) for every node, keyed by entity id.
std::map< ew::core::foundation::ContentId, QPointF > ew::app::relationship::radialLayout(const RelationshipGraph &graph, ew::core::foundation::ContentId focusId, const std::map< ew::core::foundation::ContentId, int > &depths, const RadialLayoutParams ¶ms={})
Computes a radial (concentric-by-depth) layout for focus mode: the focus node sits at the centre, and every other node is placed on the ring for its hop distance from the focus – depth-1 on the first ring, depth-2 on the next, and so on – with the nodes on each ring spread evenly around it. This is the ego-graph shape for "who is connected to this entity, and how
far". depths is the nodeDepths map for focusId (so the caller chooses the direction); only nodes of graph that appear in depths are placed. Deterministic: ring order follows the node order in graph.