// Convert FixtureData → React Flow nodes/edges. // Positions in the fixture are normalized 0..1 against a 720×460 board; we map // to absolute pixel coordinates so React Flow can render them. import { MarkerType, type Edge, type Node } from "@xyflow/react"; import type { BlockNodeData } from "./nodes/BlockNode"; import type { SysmlEdgeData } from "./edges/SysmlEdge"; import type { FixtureData, AssociationKind } from "../../lib/fixtures/aristotle"; const BOARD_W = 720; const BOARD_H = 460; function markerForKind(kind: AssociationKind) { if (kind === "composition") { // React Flow doesn't ship a diamond marker; use the closed-arrow as the // closest stock alternative. M3 ships this — a custom diamond can be // added later if needed. return { type: MarkerType.ArrowClosed, color: "var(--edge)", width: 18, height: 18 }; } if (kind === "constraint") return undefined; return { type: MarkerType.Arrow, color: "var(--edge)", width: 18, height: 18 }; } export function fixtureToFlow(data: FixtureData): { nodes: Node[]; edges: Edge[] } { const nodes: Node[] = data.blocks.map(b => ({ id: b.id, type: "sysmlBlock", position: { x: b.x * BOARD_W, y: b.y * BOARD_H }, data: { label: b.label, kind: b.kind, properties: [...b.properties], ...(b.kind === "constraint" ? { expression: "{ tenancy = institutional }" } : {}), }, })); const edges: Edge[] = data.associations.map(a => ({ id: a.id, source: a.from, target: a.to, type: "sysml", data: { label: a.label, kind: a.kind }, markerEnd: markerForKind(a.kind), })); return { nodes, edges }; }