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 &params={})

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 &params={})

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.