feat(docs): add useLayout composable
This commit is contained in:
@@ -18,7 +18,7 @@ import { IntersectionApp, IntersectionCSS } from './intersection'
|
|||||||
import { SnapToHandleApp, SnappableConnectionLine } from './connection-radius'
|
import { SnapToHandleApp, SnappableConnectionLine } from './connection-radius'
|
||||||
import { NodeResizerApp, ResizableNode } from './node-resizer'
|
import { NodeResizerApp, ResizableNode } from './node-resizer'
|
||||||
import { ToolbarApp, ToolbarNode } from './node-toolbar'
|
import { ToolbarApp, ToolbarNode } from './node-toolbar'
|
||||||
import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useRunProcess, useShuffle } from './layout'
|
import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useLayout, useRunProcess, useShuffle } from './layout'
|
||||||
|
|
||||||
export const exampleImports = {
|
export const exampleImports = {
|
||||||
basic: {
|
basic: {
|
||||||
@@ -131,6 +131,7 @@ export const exampleImports = {
|
|||||||
'AnimationEdge.vue': LayoutEdge,
|
'AnimationEdge.vue': LayoutEdge,
|
||||||
'useRunProcess.js': useRunProcess,
|
'useRunProcess.js': useRunProcess,
|
||||||
'useShuffle.js': useShuffle,
|
'useShuffle.js': useShuffle,
|
||||||
|
'useLayout.js': useLayout,
|
||||||
'Icon.vue': LayoutIcon,
|
'Icon.vue': LayoutIcon,
|
||||||
'additionalImports': {
|
'additionalImports': {
|
||||||
'@vueuse/core': 'https://cdn.jsdelivr.net/npm/@vueuse/core@10.7.0/index.mjs',
|
'@vueuse/core': 'https://cdn.jsdelivr.net/npm/@vueuse/core@10.7.0/index.mjs',
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dagre from 'dagre'
|
|
||||||
import { nextTick, ref } from 'vue'
|
import { nextTick, ref } from 'vue'
|
||||||
import { Panel, Position, VueFlow, useVueFlow } from '@vue-flow/core'
|
import { Panel, VueFlow, useVueFlow } from '@vue-flow/core'
|
||||||
import { Background } from '@vue-flow/background'
|
import { Background } from '@vue-flow/background'
|
||||||
import Icon from './Icon.vue'
|
import Icon from './Icon.vue'
|
||||||
import ProcessNode from './ProcessNode.vue'
|
import ProcessNode from './ProcessNode.vue'
|
||||||
@@ -10,75 +9,50 @@ import AnimationEdge from './AnimationEdge.vue'
|
|||||||
import { initialEdges, initialNodes } from './initial-elements.js'
|
import { initialEdges, initialNodes } from './initial-elements.js'
|
||||||
import { useRunProcess } from './useRunProcess'
|
import { useRunProcess } from './useRunProcess'
|
||||||
import { useShuffle } from './useShuffle'
|
import { useShuffle } from './useShuffle'
|
||||||
|
import { useLayout } from './useLayout'
|
||||||
|
|
||||||
const nodes = ref(initialNodes)
|
const nodes = ref(initialNodes)
|
||||||
|
|
||||||
const edges = ref(initialEdges)
|
const edges = ref(initialEdges)
|
||||||
|
|
||||||
const dagreGraph = ref(new dagre.graphlib.Graph())
|
|
||||||
|
|
||||||
const cancelOnError = ref(true)
|
const cancelOnError = ref(true)
|
||||||
|
|
||||||
const shuffle = useShuffle()
|
const shuffle = useShuffle()
|
||||||
|
|
||||||
const { run, stop, reset, isRunning } = useRunProcess({ dagreGraph, cancelOnError })
|
const { graph, layout, previousDirection } = useLayout()
|
||||||
|
|
||||||
const { findNode, fitView } = useVueFlow()
|
const { run, stop, reset, isRunning } = useRunProcess({ graph, cancelOnError })
|
||||||
|
|
||||||
function handleLayout(direction) {
|
const { fitView } = useVueFlow()
|
||||||
// we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
|
|
||||||
dagreGraph.value = new dagre.graphlib.Graph()
|
|
||||||
|
|
||||||
dagreGraph.value.setDefaultEdgeLabel(() => ({}))
|
async function shuffleGraph() {
|
||||||
|
await stop()
|
||||||
|
|
||||||
const isHorizontal = direction === 'LR'
|
|
||||||
dagreGraph.value.setGraph({ rankdir: direction })
|
|
||||||
|
|
||||||
for (const node of nodes.value) {
|
|
||||||
// if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
|
|
||||||
const graphNode = findNode(node.id)
|
|
||||||
|
|
||||||
dagreGraph.value.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(edges.value)
|
|
||||||
for (const edge of edges.value) {
|
|
||||||
dagreGraph.value.setEdge(edge.source, edge.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
dagre.layout(dagreGraph.value)
|
|
||||||
|
|
||||||
// set nodes with updated positions
|
|
||||||
nodes.value = nodes.value.map((node) => {
|
|
||||||
const nodeWithPosition = dagreGraph.value.node(node.id)
|
|
||||||
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
||||||
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
||||||
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
fitView()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function shuffleGraph() {
|
|
||||||
reset(nodes.value)
|
reset(nodes.value)
|
||||||
|
|
||||||
edges.value = shuffle(nodes.value)
|
edges.value = shuffle(nodes.value)
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
handleLayout('LR')
|
layoutGraph(previousDirection.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function layoutGraph(direction) {
|
||||||
|
await stop()
|
||||||
|
|
||||||
|
reset(nodes.value)
|
||||||
|
|
||||||
|
nodes.value = layout(nodes.value, edges.value, direction)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
fitView()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layoutflow">
|
<div class="layoutflow">
|
||||||
<VueFlow :nodes="nodes" :edges="edges" @nodes-initialized="handleLayout('LR')">
|
<VueFlow :nodes="nodes" :edges="edges" @nodes-initialized="layoutGraph('LR')">
|
||||||
<template #node-process="props">
|
<template #node-process="props">
|
||||||
<ProcessNode :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" />
|
<ProcessNode :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" />
|
||||||
</template>
|
</template>
|
||||||
@@ -108,11 +82,11 @@ function shuffleGraph() {
|
|||||||
<Icon name="play" />
|
<Icon name="play" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button title="set horizontal layout" @click="handleLayout('LR')">
|
<button title="set horizontal layout" @click="layoutGraph('LR')">
|
||||||
<Icon name="horizontal" />
|
<Icon name="horizontal" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button title="set vertical layout" @click="handleLayout('TB')">
|
<button title="set vertical layout" @click="layoutGraph('TB')">
|
||||||
<Icon name="vertical" />
|
<Icon name="vertical" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export { default as LayoutNode } from './ProcessNode.vue?raw'
|
|||||||
export { default as LayoutEdge } from './AnimationEdge.vue?raw'
|
export { default as LayoutEdge } from './AnimationEdge.vue?raw'
|
||||||
export { default as useRunProcess } from './useRunProcess.js?raw'
|
export { default as useRunProcess } from './useRunProcess.js?raw'
|
||||||
export { default as useShuffle } from './useShuffle.js?raw'
|
export { default as useShuffle } from './useShuffle.js?raw'
|
||||||
|
export { default as useLayout } from './useLayout.js?raw'
|
||||||
export { default as LayoutIcon } from './Icon.vue?raw'
|
export { default as LayoutIcon } from './Icon.vue?raw'
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import dagre from 'dagre'
|
||||||
|
import { Position, useVueFlow } from '@vue-flow/core'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable to run the layout algorithm on the graph.
|
||||||
|
* It uses the `dagre` library to calculate the layout of the nodes and edges.
|
||||||
|
*/
|
||||||
|
export function useLayout() {
|
||||||
|
const { findNode } = useVueFlow()
|
||||||
|
|
||||||
|
const graph = ref(new dagre.graphlib.Graph())
|
||||||
|
|
||||||
|
const previousDirection = ref('LR')
|
||||||
|
|
||||||
|
function layout(nodes, edges, direction) {
|
||||||
|
// we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
|
||||||
|
const dagreGraph = new dagre.graphlib.Graph()
|
||||||
|
|
||||||
|
graph.value = dagreGraph
|
||||||
|
|
||||||
|
dagreGraph.setDefaultEdgeLabel(() => ({}))
|
||||||
|
|
||||||
|
const isHorizontal = direction === 'LR'
|
||||||
|
dagreGraph.setGraph({ rankdir: direction })
|
||||||
|
|
||||||
|
previousDirection.value = direction
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
// if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
|
||||||
|
const graphNode = findNode(node.id)
|
||||||
|
|
||||||
|
dagreGraph.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 })
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const edge of edges) {
|
||||||
|
dagreGraph.setEdge(edge.source, edge.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
dagre.layout(dagreGraph)
|
||||||
|
|
||||||
|
// set nodes with updated positions
|
||||||
|
return nodes.map((node) => {
|
||||||
|
const nodeWithPosition = dagreGraph.node(node.id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
||||||
|
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
||||||
|
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { graph, layout, previousDirection }
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import { useVueFlow } from '@vue-flow/core'
|
|||||||
*
|
*
|
||||||
* When a node has multiple descendants, it will run them in parallel.
|
* When a node has multiple descendants, it will run them in parallel.
|
||||||
*/
|
*/
|
||||||
export function useRunProcess({ dagreGraph, cancelOnError = true }) {
|
export function useRunProcess({ graph: dagreGraph, cancelOnError = true }) {
|
||||||
const { updateNodeData, getConnectedEdges } = useVueFlow()
|
const { updateNodeData, getConnectedEdges } = useVueFlow()
|
||||||
|
|
||||||
const graph = toRef(() => toValue(dagreGraph))
|
const graph = toRef(() => toValue(dagreGraph))
|
||||||
@@ -127,21 +127,21 @@ export function useRunProcess({ dagreGraph, cancelOnError = true }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
async function stop() {
|
||||||
isRunning.value = false
|
isRunning.value = false
|
||||||
|
|
||||||
for (const nodeId of upcomingTasks) {
|
for (const nodeId of upcomingTasks) {
|
||||||
clearTimeout(runningTasks.get(nodeId))
|
clearTimeout(runningTasks.get(nodeId))
|
||||||
runningTasks.delete(nodeId)
|
runningTasks.delete(nodeId)
|
||||||
updateNodeData(nodeId, { isRunning: false, isFinished: false, hasError: false, isSkipped: false, isCancelled: true })
|
updateNodeData(nodeId, { isRunning: false, isFinished: false, hasError: false, isSkipped: false, isCancelled: true })
|
||||||
skipDescendants(nodeId)
|
await skipDescendants(nodeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [nodeId, task] of runningTasks) {
|
for (const [nodeId, task] of runningTasks) {
|
||||||
clearTimeout(task)
|
clearTimeout(task)
|
||||||
runningTasks.delete(nodeId)
|
runningTasks.delete(nodeId)
|
||||||
updateNodeData(nodeId, { isRunning: false, isFinished: false, hasError: false, isSkipped: false, isCancelled: true })
|
updateNodeData(nodeId, { isRunning: false, isFinished: false, hasError: false, isSkipped: false, isCancelled: true })
|
||||||
skipDescendants(nodeId)
|
await skipDescendants(nodeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
executedNodes.clear()
|
executedNodes.clear()
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ function generatePossibleEdges(nodes) {
|
|||||||
return possibleEdges
|
return possibleEdges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to create a new shuffled edges array
|
/**
|
||||||
|
* Composable that returns a function to shuffle the edges of a graph
|
||||||
|
*
|
||||||
|
* @returns {Function} A function that takes a list of nodes and returns a list of edges
|
||||||
|
*/
|
||||||
export function useShuffle() {
|
export function useShuffle() {
|
||||||
return (nodes) => {
|
return (nodes) => {
|
||||||
const possibleEdges = generatePossibleEdges(nodes)
|
const possibleEdges = generatePossibleEdges(nodes)
|
||||||
|
|||||||
Reference in New Issue
Block a user