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:
Braks
2024-11-05 15:53:16 +03:00
committed by GitHub
parent f3075bc246
commit 61f4b0d447
13 changed files with 959 additions and 363 deletions

View File

@@ -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,

View 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>

View 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>

View 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'

View 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' },
]

View 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 }
}

View File

@@ -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>

View File

@@ -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' },
]