Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | 7x 7x 7x 7x 7x 7x 126x 50x 76x 28x 7x 21x 21x 21x 21x 63x 63x 21x 40x 21x 21x 63x 63x 63x 21x 28x | /**
* Dagre positioning layer for the Coordination graph. Takes the pure
* {@link buildCoordinationGraph} model (dagre-free, unit-testable) and assigns
* deterministic node positions via a left-to-right bipartite dagre layout.
*
* @module
*/
import { useMemo } from "react";
import dagre from "@dagrejs/dagre";
import type { Node } from "@xyflow/react";
import type { Session, StreamData } from "../../hooks/types.js";
import {
buildCoordinationGraph,
STREAM_NODE_TYPE,
type CoordNodeData,
type CoordinationLayoutResult,
} from "./coordinationGraphModel.js";
export {
buildCoordinationGraph,
sessionNodeId,
streamNodeId,
COORD_EDGE_TYPE,
SESSION_NODE_TYPE,
STREAM_NODE_TYPE,
} from "./coordinationGraphModel.js";
export type {
CoordEdgeData,
CoordNodeData,
CoordinationLayoutResult,
SessionNodeData,
StreamNodeData,
} from "./coordinationGraphModel.js";
/** Width of a session node (pixels). */
const SESSION_NODE_WIDTH: number = 200;
/** Height of a session node (pixels). */
const SESSION_NODE_HEIGHT: number = 64;
/** Width of a stream hub node (pixels). */
const STREAM_NODE_WIDTH: number = 180;
/** Height of a stream hub node (pixels). */
const STREAM_NODE_HEIGHT: number = 56;
/** Separation between sibling nodes within a rank (pixels). */
const NODE_SEPARATION: number = 40;
/** Separation between rank levels (pixels). */
const RANK_SEPARATION: number = 70;
/** Node dimensions for dagre, keyed by node type. */
function nodeDimensions(type: string | undefined): { width: number; height: number } {
if (type === STREAM_NODE_TYPE) {
return { width: STREAM_NODE_WIDTH, height: STREAM_NODE_HEIGHT };
}
return { width: SESSION_NODE_WIDTH, height: SESSION_NODE_HEIGHT };
}
/** Assign dagre positions to a pure coordination graph model. */
function layoutGraph(model: CoordinationLayoutResult): CoordinationLayoutResult {
if (model.nodes.length === 0) {
return model;
}
const graph = new dagre.graphlib.Graph({ multigraph: true });
graph.setDefaultEdgeLabel(() => ({}));
graph.setGraph({ rankdir: "LR", nodesep: NODE_SEPARATION, ranksep: RANK_SEPARATION });
for (const node of model.nodes) {
const { width, height } = nodeDimensions(node.type);
graph.setNode(node.id, { width, height });
}
for (const edge of model.edges) {
graph.setEdge(edge.source, edge.target, {}, edge.id);
}
dagre.layout(graph);
const nodes: Node<CoordNodeData>[] = model.nodes.map((node) => {
const pos = graph.node(node.id) as { x: number; y: number };
const { width, height } = nodeDimensions(node.type);
return { ...node, position: { x: pos.x - width / 2, y: pos.y - height / 2 } };
});
return { nodes, edges: model.edges };
}
/**
* Build and lay out the coordination graph, memoized on its inputs. Sessions
* with no visible streams are intentionally omitted — the graph shows
* coordination, not the full session inventory.
*/
export function useCoordinationLayout(
streams: StreamData[],
sessions: Session[],
): CoordinationLayoutResult {
return useMemo(() => layoutGraph(buildCoordinationGraph(streams, sessions)), [streams, sessions]);
}
|