Add Vue Flow graph visualizer

This commit is contained in:
2026-06-18 23:45:33 +02:00
parent 2083546311
commit 7b814b440f
19 changed files with 2708 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
import type { Edge, Node, Position } from '@vue-flow/core'
import dagre from '@dagrejs/dagre'
const DEFAULT_NODE_WIDTH = 280
const DEFAULT_ROW_HEIGHT = 34
const DEFAULT_HEADER_HEIGHT = 88
const MIN_NODE_HEIGHT = 150
type LayoutDirection = 'LR' | 'TB'
type LayoutOptions = {
direction?: LayoutDirection
useSavedPositions?: boolean
}
function estimateNodeHeight(node: Node) {
const inputs = Array.isArray(node.data?.inputs) ? node.data.inputs.length : 0
const outputs = Array.isArray(node.data?.outputs) ? node.data.outputs.length : 0
return Math.max(MIN_NODE_HEIGHT, DEFAULT_HEADER_HEIGHT + Math.max(inputs, outputs, 1) * DEFAULT_ROW_HEIGHT)
}
function targetPosition(direction: LayoutDirection): Position {
return direction === 'LR' ? 'left' as Position : 'top' as Position
}
function sourcePosition(direction: LayoutDirection): Position {
return direction === 'LR' ? 'right' as Position : 'bottom' as Position
}
export function layoutGraph(nodes: Node[], edges: Edge[], options: LayoutOptions = {}) {
const direction = options.direction || 'LR'
if (options.useSavedPositions && nodes.every((node) => node.data?.hasSavedPosition)) {
return nodes.map((node) => ({
...node,
targetPosition: targetPosition(direction),
sourcePosition: sourcePosition(direction),
}))
}
const graph = new dagre.graphlib.Graph()
graph.setDefaultEdgeLabel(() => ({}))
graph.setGraph({
rankdir: direction,
nodesep: 90,
ranksep: 130,
marginx: 40,
marginy: 40,
})
for (const node of nodes) {
graph.setNode(node.id, {
width: DEFAULT_NODE_WIDTH,
height: estimateNodeHeight(node),
})
}
for (const edge of edges) {
graph.setEdge(edge.source, edge.target)
}
dagre.layout(graph)
return nodes.map((node) => {
const graphNode = graph.node(node.id)
const height = estimateNodeHeight(node)
return {
...node,
targetPosition: targetPosition(direction),
sourcePosition: sourcePosition(direction),
position: graphNode
? { x: graphNode.x - DEFAULT_NODE_WIDTH / 2, y: graphNode.y - height / 2 }
: node.position,
}
})
}