docs(examples): add simple layout example (#1678)
* docs(examples): cleanup animated layout example Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> * chore(docs,deps): update deps Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> * docs(examples): add animated and simple layout examples Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> --------- Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,8 @@ import { SnapToHandleApp, SnappableConnectionLine } from './connection-radius'
|
||||
import { NodeResizerApp, ResizableNode } from './node-resizer'
|
||||
import { ToolbarApp, ToolbarNode } from './node-toolbar'
|
||||
import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useLayout, useRunProcess, useShuffle } from './layout'
|
||||
import { SimpleLayoutApp, SimpleLayoutElements, SimpleLayoutIcon, useSimpleLayout } from './layout-simple'
|
||||
|
||||
import { MathApp, MathCSS, MathElements, MathIcon, MathOperatorNode, MathResultNode, MathValueNode } from './math'
|
||||
import { ConfirmApp, ConfirmDialog, useDialog } from './confirm-delete'
|
||||
|
||||
@@ -140,6 +142,15 @@ export const exampleImports = {
|
||||
'@dagrejs/dagre': 'https://cdn.jsdelivr.net/npm/@dagrejs/dagre@1.1.2/+esm',
|
||||
},
|
||||
},
|
||||
layoutSimple: {
|
||||
'App.vue': SimpleLayoutApp,
|
||||
'initial-elements.js': SimpleLayoutElements,
|
||||
'useLayout.js': useSimpleLayout,
|
||||
'Icon.vue': SimpleLayoutIcon,
|
||||
'additionalImports': {
|
||||
'@dagrejs/dagre': 'https://cdn.jsdelivr.net/npm/@dagrejs/dagre@1.1.2/+esm',
|
||||
},
|
||||
},
|
||||
math: {
|
||||
'App.vue': MathApp,
|
||||
'ValueNode.vue': MathValueNode,
|
||||
|
||||
133
docs/examples/layout-simple/App.vue
Normal file
133
docs/examples/layout-simple/App.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<script setup>
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { Panel, VueFlow, useVueFlow } from '@vue-flow/core'
|
||||
import { Background } from '@vue-flow/background'
|
||||
import Icon from './Icon.vue'
|
||||
|
||||
import { initialEdges, initialNodes } from './initial-elements.js'
|
||||
import { useLayout } from './useLayout'
|
||||
|
||||
const nodes = ref(initialNodes)
|
||||
|
||||
const edges = ref(initialEdges)
|
||||
|
||||
const { layout } = useLayout()
|
||||
|
||||
const { fitView } = useVueFlow()
|
||||
|
||||
async function layoutGraph(direction) {
|
||||
nodes.value = layout(nodes.value, edges.value, direction)
|
||||
|
||||
nextTick(() => {
|
||||
fitView()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout-flow">
|
||||
<VueFlow :nodes="nodes" :edges="edges" @nodes-initialized="layoutGraph('LR')">
|
||||
<Background />
|
||||
|
||||
<Panel class="process-panel" position="top-right">
|
||||
<div class="layout-panel">
|
||||
<button title="set horizontal layout" @click="layoutGraph('LR')">
|
||||
<Icon name="horizontal" />
|
||||
</button>
|
||||
|
||||
<button title="set vertical layout" @click="layoutGraph('TB')">
|
||||
<Icon name="vertical" />
|
||||
</button>
|
||||
</div>
|
||||
</Panel>
|
||||
</VueFlow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.layout-flow {
|
||||
background-color: #1a192b;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.process-panel,
|
||||
.layout-panel {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.process-panel {
|
||||
background-color: #2d3748;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.process-panel button {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: #4a5568;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.process-panel button {
|
||||
font-size: 16px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.checkbox-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.process-panel button:hover,
|
||||
.layout-panel button:hover {
|
||||
background-color: #2563eb;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.process-panel label {
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.stop-btn svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stop-btn:hover svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stop-btn:hover .spinner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #2563eb;
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
40
docs/examples/layout-simple/Icon.vue
Normal file
40
docs/examples/layout-simple/Icon.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg v-if="name === 'play'" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5v14l11-7z" fill="currentColor" />
|
||||
</svg>
|
||||
|
||||
<svg v-else-if="name === 'stop'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg v-else-if="name === 'horizontal'" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2,12 L22,12" stroke="currentColor" stroke-width="2" />
|
||||
<path d="M7,7 L2,12 L7,17" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
<path d="M17,7 L22,12 L17,17" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
</svg>
|
||||
|
||||
<svg v-else-if="name === 'vertical'" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12,2 L12,22" stroke="currentColor" stroke-width="2" />
|
||||
<path d="M7,7 L12,2 L17,7" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
<path d="M7,17 L12,22 L17,17" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
</svg>
|
||||
|
||||
<svg v-else-if="name === 'shuffle'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
4
docs/examples/layout-simple/index.ts
Normal file
4
docs/examples/layout-simple/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as SimpleLayoutApp } from './App.vue?raw'
|
||||
export { default as SimpleLayoutElements } from './initial-elements.js?raw'
|
||||
export { default as useSimpleLayout } from './useLayout.js?raw'
|
||||
export { default as SimpleLayoutIcon } from './Icon.vue?raw'
|
||||
94
docs/examples/layout-simple/initial-elements.js
Normal file
94
docs/examples/layout-simple/initial-elements.js
Normal file
@@ -0,0 +1,94 @@
|
||||
const position = { x: 0, y: 0 }
|
||||
|
||||
export const initialNodes = [
|
||||
{
|
||||
id: '1',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 2a',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2b',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 2b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2c',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 2c',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2d',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 2d',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 3',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 4',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 5',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 6',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
position,
|
||||
data: {
|
||||
label: 'Node 7',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const initialEdges = [
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e2-2a', source: '2', target: '2a' },
|
||||
{ id: 'e2-2b', source: '2', target: '2b' },
|
||||
{ id: 'e2-2c', source: '2', target: '2c' },
|
||||
{ id: 'e2c-2d', source: '2c', target: '2d' },
|
||||
{ id: 'e3-7', source: '3', target: '4' },
|
||||
{ id: 'e4-5', source: '4', target: '5' },
|
||||
{ id: 'e5-6', source: '5', target: '6' },
|
||||
{ id: 'e5-7', source: '5', target: '7' },
|
||||
]
|
||||
56
docs/examples/layout-simple/useLayout.js
Normal file
56
docs/examples/layout-simple/useLayout.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import dagre from '@dagrejs/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 }
|
||||
}
|
||||
@@ -52,7 +52,12 @@ async function layoutGraph(direction) {
|
||||
|
||||
<template>
|
||||
<div class="layout-flow">
|
||||
<VueFlow :nodes="nodes" :edges="edges" @nodes-initialized="layoutGraph('LR')">
|
||||
<VueFlow
|
||||
:nodes="nodes"
|
||||
:edges="edges"
|
||||
:default-edge-options="{ type: 'animation', animated: true }"
|
||||
@nodes-initialized="layoutGraph('LR')"
|
||||
>
|
||||
<template #node-process="props">
|
||||
<ProcessNode :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" />
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const position = { x: 0, y: 0 }
|
||||
const nodeType = 'process'
|
||||
const edgeType = 'animation'
|
||||
|
||||
export const initialNodes = [
|
||||
{
|
||||
@@ -61,14 +60,14 @@ export const initialNodes = [
|
||||
]
|
||||
|
||||
export const initialEdges = [
|
||||
{ id: 'e1-2', source: '1', target: '2', type: edgeType, animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3', type: edgeType, animated: true },
|
||||
{ id: 'e2-2a', source: '2', target: '2a', type: edgeType, animated: true },
|
||||
{ id: 'e2-2b', source: '2', target: '2b', type: edgeType, animated: true },
|
||||
{ id: 'e2-2c', source: '2', target: '2c', type: edgeType, animated: true },
|
||||
{ id: 'e2c-2d', source: '2c', target: '2d', type: edgeType, animated: true },
|
||||
{ id: 'e3-7', source: '3', target: '4', type: edgeType, animated: true },
|
||||
{ id: 'e4-5', source: '4', target: '5', type: edgeType, animated: true },
|
||||
{ id: 'e5-6', source: '5', target: '6', type: edgeType, animated: true },
|
||||
{ id: 'e5-7', source: '5', target: '7', type: edgeType, animated: true },
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e2-2a', source: '2', target: '2a' },
|
||||
{ id: 'e2-2b', source: '2', target: '2b' },
|
||||
{ id: 'e2-2c', source: '2', target: '2c' },
|
||||
{ id: 'e2c-2d', source: '2c', target: '2d' },
|
||||
{ id: 'e3-7', source: '3', target: '4' },
|
||||
{ id: 'e4-5', source: '4', target: '5' },
|
||||
{ id: 'e5-6', source: '5', target: '6' },
|
||||
{ id: 'e5-7', source: '5', target: '7' },
|
||||
]
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
"typedocs": "typedoc --options ./typedoc.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^5.7.0",
|
||||
"@algolia/client-search": "^5.12.0",
|
||||
"@stackblitz/sdk": "^1.11.0",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/speed-insights": "^1.0.12",
|
||||
"@vercel/analytics": "^1.3.2",
|
||||
"@vercel/speed-insights": "^1.0.14",
|
||||
"@vue-flow/background": "workspace:*",
|
||||
"@vue-flow/controls": "workspace:*",
|
||||
"@vue-flow/core": "workspace:*",
|
||||
@@ -23,24 +23,24 @@
|
||||
"@vue-flow/node-toolbar": "workspace:*",
|
||||
"@vue/repl": "3.4.0",
|
||||
"blobity": "^0.2.3",
|
||||
"vue": "^3.5.11",
|
||||
"web-vitals": "^4.2.3"
|
||||
"vue": "^3.5.12",
|
||||
"web-vitals": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.256",
|
||||
"@iconify/json": "^2.2.268",
|
||||
"@tooling/eslint-config": "workspace:*",
|
||||
"@tooling/tsconfig": "workspace:*",
|
||||
"@windicss/plugin-scrollbar": "^1.2.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"ohmyfetch": "^0.4.21",
|
||||
"typedoc": "^0.26.7",
|
||||
"typedoc-plugin-markdown": "^4.2.9",
|
||||
"typedoc-plugin-merge-modules": "^6.0.1",
|
||||
"typedoc": "^0.26.11",
|
||||
"typedoc-plugin-markdown": "^4.2.10",
|
||||
"typedoc-plugin-merge-modules": "^6.0.3",
|
||||
"unplugin-auto-import": "^0.18.3",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"unplugin-icons": "^0.20.0",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite-plugin-windicss": "^1.9.3",
|
||||
"vitepress": "^1.3.4",
|
||||
"vitepress": "^1.5.0",
|
||||
"windicss": "^3.5.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,6 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Basic', link: '/examples/' },
|
||||
{ text: 'Layouting', link: '/examples/layout' },
|
||||
{ text: 'Drag & Drop', link: '/examples/dnd' },
|
||||
{ text: 'Interactions', link: '/examples/interaction' },
|
||||
{ text: 'Save & Restore', link: '/examples/save' },
|
||||
@@ -217,6 +216,14 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
|
||||
{ text: 'Stress Test', link: '/examples/stress' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Layout',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Simple Layout', link: '/examples/layout/simple' },
|
||||
{ text: 'Animation & Layout', link: '/examples/layout/animated' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Nodes',
|
||||
collapsed: false,
|
||||
|
||||
10
docs/src/examples/layout/animated.md
Normal file
10
docs/src/examples/layout/animated.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Animation and Layout
|
||||
|
||||
In this example we are going to use [dagre](https://github.com/dagrejs/dagre) to layout our nodes and also implement
|
||||
some animations
|
||||
to demonstrate execution from one node to another in a sequence.
|
||||
|
||||
<div class="mt-6">
|
||||
<Repl example="layout"></Repl>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Layouting
|
||||
# Layout
|
||||
|
||||
A lot of the time, you'll want to layout your nodes automatically instead of assigning their positions manually.
|
||||
Vue Flow does *not* have a built-in layouting system, but it's easy to use a third-party library to achieve this.
|
||||
@@ -6,5 +6,5 @@ Vue Flow does *not* have a built-in layouting system, but it's easy to use a thi
|
||||
In this example we are going to use [dagre](https://github.com/dagrejs/dagre) to layout our nodes.
|
||||
|
||||
<div class="mt-6">
|
||||
<Repl example="layout"></Repl>
|
||||
<Repl example="layoutSimple"></Repl>
|
||||
</div>
|
||||
Reference in New Issue
Block a user