import { onMount } from "solid-js"; import { Graph, Cell, Shape, Edge, Node } from "@antv/x6"; import { PortManager } from "@antv/x6/lib/model/port"; import { cn } from "@/lib/utils"; export const ER_NODE_NAME = "er-rect"; export const LINE_HEIGHT = 24; export const NODE_WIDTH = 250; const ACCENT_600 = "#0073aa"; const GRAY_100 = "#f3f7f9"; export const EDGE_COLOR = "#A2B1C3"; export type NodeMetadata = Node.Metadata; export type EdgeMetadata = Edge.Metadata; export type PortMetadata = PortManager.PortMetadata; (function setupGraph() { const ER_PORT_POSITION_NAME = "erPortPosition"; Graph.registerPortLayout( ER_PORT_POSITION_NAME, (portsPositionArgs) => { return portsPositionArgs.map((_, index) => { return { position: { x: 0, y: (index + 1) * LINE_HEIGHT, }, angle: 0, }; }); }, true, ); Graph.registerNode( ER_NODE_NAME, { inherit: "rect", markup: [ { tagName: "rect", selector: "body", }, { tagName: "text", selector: "label", }, ], attrs: { rect: { strokeWidth: 1, stroke: ACCENT_600, fill: ACCENT_600, }, label: { fontWeight: "bold", fill: "white", fontSize: 12, }, }, ports: { groups: { list: { markup: [ { tagName: "rect", selector: "portBody", }, { tagName: "text", selector: "portNameLabel", }, { tagName: "text", selector: "portTypeLabel", }, ], attrs: { portBody: { width: NODE_WIDTH, height: LINE_HEIGHT, strokeWidth: 1, stroke: ACCENT_600, fill: GRAY_100, magnet: true, }, portNameLabel: { ref: "portBody", refX: 6, refY: 6, fontSize: 10, }, portTypeLabel: { ref: "portBody", refX: 95, refY: 6, fontSize: 10, }, }, position: ER_PORT_POSITION_NAME, }, }, }, }, true, ); })(); function createEdge(): Edge { return new Shape.Edge({ attrs: { line: { stroke: EDGE_COLOR, strokeWidth: 2, }, }, }); } export function ErdGraph(props: { class?: string; nodes: NodeMetadata[]; edges: EdgeMetadata[]; }) { let ref: HTMLDivElement | undefined; onMount(() => { const graph = new Graph({ container: ref, grid: { visible: true, }, autoResize: true, interacting: { edgeLabelMovable: false, magnetConnectable: false, }, connecting: { connector: "rounded", router: { name: "er", args: { offset: 25, direction: "H", }, }, createEdge, }, panning: { enabled: true, }, mousewheel: { enabled: true, // modifiers: ['ctrl', 'meta'], minScale: 0.5, maxScale: 2, }, }); // Implement our own simple grid layout since @antv/layout seems to be out of sync: // // v0.3.25 results in "layout is not a function": https://github.com/antvis/X6/issues/4441 // v1.2 has completely in-compatible APIs. They'll probably need to overhaul x6 first. const aspect = window.innerWidth / window.innerHeight; const size = Math.ceil(Math.sqrt(props.nodes.length) * aspect); const maxHeight = props.nodes.reduce((acc, node) => { const ports = node.ports; let numPorts = 0; if (ports instanceof Array) { numPorts = ports.length; } else if (ports !== undefined) { numPorts = 1; } return Math.max(acc, (numPorts + 1) * LINE_HEIGHT); }, 0); const cells: Cell[] = [ ...props.nodes.map((n, index) => { // Scatter nodes if no explicit position is already set. if (n.position === undefined) { const MARGIN = 80; const col = index % size; const row = Math.floor(index / size); n.position = { x: col * (NODE_WIDTH + MARGIN), y: row * (maxHeight + MARGIN), }; } return graph.createNode(n); }), ...props.edges.map((e) => graph.createEdge(e)), ]; graph.resetCells(cells); graph.zoomToFit({ padding: 100, maxScale: 1 }); }); return (
); }