Skip to content

Commit 61f4b0d

Browse files
authored
docs(examples): add simple layout example (#1678)
* docs(examples): cleanup animated layout example Signed-off-by: braks <[email protected]> * chore(docs,deps): update deps Signed-off-by: braks <[email protected]> * docs(examples): add animated and simple layout examples Signed-off-by: braks <[email protected]> --------- Signed-off-by: braks <[email protected]>
1 parent f3075bc commit 61f4b0d

File tree

13 files changed

+947
-351
lines changed

13 files changed

+947
-351
lines changed

docs/examples/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { SnapToHandleApp, SnappableConnectionLine } from './connection-radius'
1919
import { NodeResizerApp, ResizableNode } from './node-resizer'
2020
import { ToolbarApp, ToolbarNode } from './node-toolbar'
2121
import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useLayout, useRunProcess, useShuffle } from './layout'
22+
import { SimpleLayoutApp, SimpleLayoutElements, SimpleLayoutIcon, useSimpleLayout } from './layout-simple'
23+
2224
import { MathApp, MathCSS, MathElements, MathIcon, MathOperatorNode, MathResultNode, MathValueNode } from './math'
2325
import { ConfirmApp, ConfirmDialog, useDialog } from './confirm-delete'
2426

@@ -140,6 +142,15 @@ export const exampleImports = {
140142
'@dagrejs/dagre': 'https://cdn.jsdelivr.net/npm/@dagrejs/[email protected]/+esm',
141143
},
142144
},
145+
layoutSimple: {
146+
'App.vue': SimpleLayoutApp,
147+
'initial-elements.js': SimpleLayoutElements,
148+
'useLayout.js': useSimpleLayout,
149+
'Icon.vue': SimpleLayoutIcon,
150+
'additionalImports': {
151+
'@dagrejs/dagre': 'https://cdn.jsdelivr.net/npm/@dagrejs/[email protected]/+esm',
152+
},
153+
},
143154
math: {
144155
'App.vue': MathApp,
145156
'ValueNode.vue': MathValueNode,

docs/examples/layout-simple/App.vue

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<script setup>
2+
import { nextTick, ref } from 'vue'
3+
import { Panel, VueFlow, useVueFlow } from '@vue-flow/core'
4+
import { Background } from '@vue-flow/background'
5+
import Icon from './Icon.vue'
6+
7+
import { initialEdges, initialNodes } from './initial-elements.js'
8+
import { useLayout } from './useLayout'
9+
10+
const nodes = ref(initialNodes)
11+
12+
const edges = ref(initialEdges)
13+
14+
const { layout } = useLayout()
15+
16+
const { fitView } = useVueFlow()
17+
18+
async function layoutGraph(direction) {
19+
nodes.value = layout(nodes.value, edges.value, direction)
20+
21+
nextTick(() => {
22+
fitView()
23+
})
24+
}
25+
</script>
26+
27+
<template>
28+
<div class="layout-flow">
29+
<VueFlow :nodes="nodes" :edges="edges" @nodes-initialized="layoutGraph('LR')">
30+
<Background />
31+
32+
<Panel class="process-panel" position="top-right">
33+
<div class="layout-panel">
34+
<button title="set horizontal layout" @click="layoutGraph('LR')">
35+
<Icon name="horizontal" />
36+
</button>
37+
38+
<button title="set vertical layout" @click="layoutGraph('TB')">
39+
<Icon name="vertical" />
40+
</button>
41+
</div>
42+
</Panel>
43+
</VueFlow>
44+
</div>
45+
</template>
46+
47+
<style>
48+
.layout-flow {
49+
background-color: #1a192b;
50+
height: 100%;
51+
width: 100%;
52+
}
53+
54+
.process-panel,
55+
.layout-panel {
56+
display: flex;
57+
gap: 10px;
58+
}
59+
60+
.process-panel {
61+
background-color: #2d3748;
62+
padding: 10px;
63+
border-radius: 8px;
64+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
65+
display: flex;
66+
flex-direction: column;
67+
}
68+
69+
.process-panel button {
70+
border: none;
71+
cursor: pointer;
72+
background-color: #4a5568;
73+
border-radius: 8px;
74+
color: white;
75+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
76+
}
77+
78+
.process-panel button {
79+
font-size: 16px;
80+
width: 40px;
81+
height: 40px;
82+
display: flex;
83+
align-items: center;
84+
justify-content: center;
85+
}
86+
87+
.checkbox-panel {
88+
display: flex;
89+
align-items: center;
90+
gap: 10px;
91+
}
92+
93+
.process-panel button:hover,
94+
.layout-panel button:hover {
95+
background-color: #2563eb;
96+
transition: background-color 0.2s;
97+
}
98+
99+
.process-panel label {
100+
color: white;
101+
font-size: 12px;
102+
}
103+
104+
.stop-btn svg {
105+
display: none;
106+
}
107+
108+
.stop-btn:hover svg {
109+
display: block;
110+
}
111+
112+
.stop-btn:hover .spinner {
113+
display: none;
114+
}
115+
116+
.spinner {
117+
border: 3px solid #f3f3f3;
118+
border-top: 3px solid #2563eb;
119+
border-radius: 50%;
120+
width: 10px;
121+
height: 10px;
122+
animation: spin 1s linear infinite;
123+
}
124+
125+
@keyframes spin {
126+
0% {
127+
transform: rotate(0deg);
128+
}
129+
100% {
130+
transform: rotate(360deg);
131+
}
132+
}
133+
</style>

docs/examples/layout-simple/Icon.vue

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script setup>
2+
defineProps({
3+
name: {
4+
type: String,
5+
required: true,
6+
},
7+
})
8+
</script>
9+
10+
<template>
11+
<svg v-if="name === 'play'" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
12+
<path d="M8 5v14l11-7z" fill="currentColor" />
13+
</svg>
14+
15+
<svg v-else-if="name === 'stop'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
16+
<path
17+
fill="currentColor"
18+
d="M8 16h8V8H8zm4 6q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22"
19+
/>
20+
</svg>
21+
22+
<svg v-else-if="name === 'horizontal'" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
23+
<path d="M2,12 L22,12" stroke="currentColor" stroke-width="2" />
24+
<path d="M7,7 L2,12 L7,17" stroke="currentColor" stroke-width="2" fill="none" />
25+
<path d="M17,7 L22,12 L17,17" stroke="currentColor" stroke-width="2" fill="none" />
26+
</svg>
27+
28+
<svg v-else-if="name === 'vertical'" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
29+
<path d="M12,2 L12,22" stroke="currentColor" stroke-width="2" />
30+
<path d="M7,7 L12,2 L17,7" stroke="currentColor" stroke-width="2" fill="none" />
31+
<path d="M7,17 L12,22 L17,17" stroke="currentColor" stroke-width="2" fill="none" />
32+
</svg>
33+
34+
<svg v-else-if="name === 'shuffle'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
35+
<path
36+
fill="currentColor"
37+
d="M14 20v-2h2.6l-3.175-3.175L14.85 13.4L18 16.55V14h2v6zm-8.6 0L4 18.6L16.6 6H14V4h6v6h-2V7.4zm3.775-9.425L4 5.4L5.4 4l5.175 5.175z"
38+
/>
39+
</svg>
40+
</template>

docs/examples/layout-simple/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { default as SimpleLayoutApp } from './App.vue?raw'
2+
export { default as SimpleLayoutElements } from './initial-elements.js?raw'
3+
export { default as useSimpleLayout } from './useLayout.js?raw'
4+
export { default as SimpleLayoutIcon } from './Icon.vue?raw'
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const position = { x: 0, y: 0 }
2+
3+
export const initialNodes = [
4+
{
5+
id: '1',
6+
position,
7+
data: {
8+
label: 'Node 1',
9+
},
10+
},
11+
{
12+
id: '2',
13+
position,
14+
data: {
15+
label: 'Node 2',
16+
},
17+
},
18+
{
19+
id: '2a',
20+
position,
21+
data: {
22+
label: 'Node 2a',
23+
},
24+
},
25+
{
26+
id: '2b',
27+
position,
28+
data: {
29+
label: 'Node 2b',
30+
},
31+
},
32+
{
33+
id: '2c',
34+
position,
35+
data: {
36+
label: 'Node 2c',
37+
},
38+
},
39+
{
40+
id: '2d',
41+
position,
42+
data: {
43+
label: 'Node 2d',
44+
},
45+
},
46+
{
47+
id: '3',
48+
position,
49+
data: {
50+
label: 'Node 3',
51+
},
52+
},
53+
{
54+
id: '4',
55+
position,
56+
data: {
57+
label: 'Node 4',
58+
},
59+
},
60+
{
61+
id: '5',
62+
position,
63+
data: {
64+
label: 'Node 5',
65+
},
66+
},
67+
{
68+
id: '6',
69+
position,
70+
data: {
71+
label: 'Node 6',
72+
},
73+
},
74+
{
75+
id: '7',
76+
position,
77+
data: {
78+
label: 'Node 7',
79+
},
80+
},
81+
]
82+
83+
export const initialEdges = [
84+
{ id: 'e1-2', source: '1', target: '2' },
85+
{ id: 'e1-3', source: '1', target: '3' },
86+
{ id: 'e2-2a', source: '2', target: '2a' },
87+
{ id: 'e2-2b', source: '2', target: '2b' },
88+
{ id: 'e2-2c', source: '2', target: '2c' },
89+
{ id: 'e2c-2d', source: '2c', target: '2d' },
90+
{ id: 'e3-7', source: '3', target: '4' },
91+
{ id: 'e4-5', source: '4', target: '5' },
92+
{ id: 'e5-6', source: '5', target: '6' },
93+
{ id: 'e5-7', source: '5', target: '7' },
94+
]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import dagre from '@dagrejs/dagre'
2+
import { Position, useVueFlow } from '@vue-flow/core'
3+
import { ref } from 'vue'
4+
5+
/**
6+
* Composable to run the layout algorithm on the graph.
7+
* It uses the `dagre` library to calculate the layout of the nodes and edges.
8+
*/
9+
export function useLayout() {
10+
const { findNode } = useVueFlow()
11+
12+
const graph = ref(new dagre.graphlib.Graph())
13+
14+
const previousDirection = ref('LR')
15+
16+
function layout(nodes, edges, direction) {
17+
// we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
18+
const dagreGraph = new dagre.graphlib.Graph()
19+
20+
graph.value = dagreGraph
21+
22+
dagreGraph.setDefaultEdgeLabel(() => ({}))
23+
24+
const isHorizontal = direction === 'LR'
25+
dagreGraph.setGraph({ rankdir: direction })
26+
27+
previousDirection.value = direction
28+
29+
for (const node of nodes) {
30+
// if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
31+
const graphNode = findNode(node.id)
32+
33+
dagreGraph.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 })
34+
}
35+
36+
for (const edge of edges) {
37+
dagreGraph.setEdge(edge.source, edge.target)
38+
}
39+
40+
dagre.layout(dagreGraph)
41+
42+
// set nodes with updated positions
43+
return nodes.map((node) => {
44+
const nodeWithPosition = dagreGraph.node(node.id)
45+
46+
return {
47+
...node,
48+
targetPosition: isHorizontal ? Position.Left : Position.Top,
49+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
50+
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
51+
}
52+
})
53+
}
54+
55+
return { graph, layout, previousDirection }
56+
}

docs/examples/layout/App.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ async function layoutGraph(direction) {
5252

5353
<template>
5454
<div class="layout-flow">
55-
<VueFlow :nodes="nodes" :edges="edges" @nodes-initialized="layoutGraph('LR')">
55+
<VueFlow
56+
:nodes="nodes"
57+
:edges="edges"
58+
:default-edge-options="{ type: 'animation', animated: true }"
59+
@nodes-initialized="layoutGraph('LR')"
60+
>
5661
<template #node-process="props">
5762
<ProcessNode :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" />
5863
</template>

0 commit comments

Comments
 (0)