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 { NodeResizerApp, ResizableNode } from './node-resizer'
|
||||
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 = {
|
||||
basic: {
|
||||
@@ -131,6 +131,7 @@ export const exampleImports = {
|
||||
'AnimationEdge.vue': LayoutEdge,
|
||||
'useRunProcess.js': useRunProcess,
|
||||
'useShuffle.js': useShuffle,
|
||||
'useLayout.js': useLayout,
|
||||
'Icon.vue': LayoutIcon,
|
||||
'additionalImports': {
|
||||
'@vueuse/core': 'https://cdn.jsdelivr.net/npm/@vueuse/core@10.7.0/index.mjs',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
import dagre from 'dagre'
|
||||
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 Icon from './Icon.vue'
|
||||
import ProcessNode from './ProcessNode.vue'
|
||||
@@ -10,75 +9,50 @@ import AnimationEdge from './AnimationEdge.vue'
|
||||
import { initialEdges, initialNodes } from './initial-elements.js'
|
||||
import { useRunProcess } from './useRunProcess'
|
||||
import { useShuffle } from './useShuffle'
|
||||
import { useLayout } from './useLayout'
|
||||
|
||||
const nodes = ref(initialNodes)
|
||||
|
||||
const edges = ref(initialEdges)
|
||||
|
||||
const dagreGraph = ref(new dagre.graphlib.Graph())
|
||||
|
||||
const cancelOnError = ref(true)
|
||||
|
||||
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) {
|
||||
// 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()
|
||||
const { fitView } = useVueFlow()
|
||||
|
||||
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)
|
||||
|
||||
edges.value = shuffle(nodes.value)
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<ProcessNode :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" />
|
||||
</template>
|
||||
@@ -108,11 +82,11 @@ function shuffleGraph() {
|
||||
<Icon name="play" />
|
||||
</button>
|
||||
|
||||
<button title="set horizontal layout" @click="handleLayout('LR')">
|
||||
<button title="set horizontal layout" @click="layoutGraph('LR')">
|
||||
<Icon name="horizontal" />
|
||||
</button>
|
||||
|
||||
<button title="set vertical layout" @click="handleLayout('TB')">
|
||||
<button title="set vertical layout" @click="layoutGraph('TB')">
|
||||
<Icon name="vertical" />
|
||||
</button>
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ export { default as LayoutNode } from './ProcessNode.vue?raw'
|
||||
export { default as LayoutEdge } from './AnimationEdge.vue?raw'
|
||||
export { default as useRunProcess } from './useRunProcess.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'
|
||||
|
||||
56
docs/examples/layout/useLayout.js
Normal file
56
docs/examples/layout/useLayout.js
Normal file
@@ -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.
|
||||
*/
|
||||
export function useRunProcess({ dagreGraph, cancelOnError = true }) {
|
||||
export function useRunProcess({ graph: dagreGraph, cancelOnError = true }) {
|
||||
const { updateNodeData, getConnectedEdges } = useVueFlow()
|
||||
|
||||
const graph = toRef(() => toValue(dagreGraph))
|
||||
@@ -127,21 +127,21 @@ export function useRunProcess({ dagreGraph, cancelOnError = true }) {
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
async function stop() {
|
||||
isRunning.value = false
|
||||
|
||||
for (const nodeId of upcomingTasks) {
|
||||
clearTimeout(runningTasks.get(nodeId))
|
||||
runningTasks.delete(nodeId)
|
||||
updateNodeData(nodeId, { isRunning: false, isFinished: false, hasError: false, isSkipped: false, isCancelled: true })
|
||||
skipDescendants(nodeId)
|
||||
await skipDescendants(nodeId)
|
||||
}
|
||||
|
||||
for (const [nodeId, task] of runningTasks) {
|
||||
clearTimeout(task)
|
||||
runningTasks.delete(nodeId)
|
||||
updateNodeData(nodeId, { isRunning: false, isFinished: false, hasError: false, isSkipped: false, isCancelled: true })
|
||||
skipDescendants(nodeId)
|
||||
await skipDescendants(nodeId)
|
||||
}
|
||||
|
||||
executedNodes.clear()
|
||||
|
||||
@@ -21,7 +21,11 @@ function generatePossibleEdges(nodes) {
|
||||
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() {
|
||||
return (nodes) => {
|
||||
const possibleEdges = generatePossibleEdges(nodes)
|
||||
|
||||
Reference in New Issue
Block a user