examples: add math example (#1461)

* examples: add math example

* docs: add math example
This commit is contained in:
Braks
2024-06-08 20:35:18 +02:00
committed by GitHub
parent f74be96b2d
commit a8ba97331f
23 changed files with 748 additions and 11 deletions

View File

@@ -19,6 +19,7 @@ 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 { MathApp, MathCSS, MathElements, MathIcon, MathOperatorNode, MathResultNode, MathValueNode } from './math'
export const exampleImports = {
basic: {
@@ -140,4 +141,13 @@ export const exampleImports = {
'@dagrejs/dagre': 'https://cdn.jsdelivr.net/npm/@dagrejs/dagre@1.1.2/+esm',
},
},
math: {
'App.vue': MathApp,
'ValueNode.vue': MathValueNode,
'OperatorNode.vue': MathOperatorNode,
'ResultNode.vue': MathResultNode,
'Icon.vue': MathIcon,
'style.css': MathCSS,
'initial-elements.js': MathElements,
},
}

View File

@@ -0,0 +1,31 @@
<script setup>
import { ref } from 'vue'
import { VueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { initialEdges, initialNodes } from './initial-elements.js'
import ValueNode from './ValueNode.vue'
import OperatorNode from './OperatorNode.vue'
import ResultNode from './ResultNode.vue'
const nodes = ref(initialNodes)
const edges = ref(initialEdges)
</script>
<template>
<VueFlow class="math-flow" :nodes="nodes" :edges="edges" fit-view-on-init>
<template #node-value="props">
<ValueNode :id="props.id" :data="props.data" />
</template>
<template #node-operator="props">
<OperatorNode :id="props.id" :data="props.data" />
</template>
<template #node-result="props">
<ResultNode :id="props.id" />
</template>
<Background />
</VueFlow>
</template>

View File

@@ -0,0 +1,31 @@
<script setup>
defineProps(['name'])
</script>
<template>
<svg v-if="name === '+'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z" />
</svg>
<svg v-else-if="name === '-'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M19 13H5v-2h14v2z" />
</svg>
<svg v-else-if="name === '*'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 12L6 6m6 6l6 6m-6-6l6-6m-6 6l-6 6"
/>
</svg>
<svg v-else-if="name === '/'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M19 13H5v-2h14zm-7-8a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0 10a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2"
/>
</svg>
</template>

View File

@@ -0,0 +1,27 @@
<script setup>
import { Handle, Position, useVueFlow } from '@vue-flow/core'
import Icon from './Icon.vue'
const props = defineProps(['id', 'data'])
const operators = ['+', '-', '*', '/']
const { updateNodeData } = useVueFlow()
</script>
<template>
<div class="buttons nodrag">
<button
v-for="operator of operators"
:key="`${id}-${operator}-operator`"
:class="{ selected: data.operator === operator }"
@click="updateNodeData(props.id, { operator })"
>
<Icon :name="operator" />
</button>
</div>
<Handle type="source" :position="Position.Right" :connectable="false" />
<Handle id="target-a" type="target" :position="Position.Left" :connectable="false" />
<Handle id="target-b" type="target" :position="Position.Left" :connectable="false" />
</template>

View File

@@ -0,0 +1,77 @@
<script setup>
import { computed } from 'vue'
import { Handle, Position, useHandleConnections, useNodesData, useVueFlow } from '@vue-flow/core'
defineProps(['id'])
const mathFunctions = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b,
}
const { getConnectedEdges } = useVueFlow()
// Get the source connections of the result node. In this example it's only one operator node.
const sourceConnections = useHandleConnections({
// type target means all connections where *this* node is the target
// that means we go backwards in the graph to find the source of the connection(s)
type: 'target',
})
// Get the source connections of the operator node
const operatorSourceConnections = computed(() =>
getConnectedEdges(sourceConnections.value[0].source).filter((e) => e.source !== sourceConnections.value[0].source),
)
const operatorData = useNodesData(() => sourceConnections.value.map((connection) => connection.source))
const valueData = useNodesData(() => operatorSourceConnections.value.map((connection) => connection.source))
const result = computed(() => {
const currResult = operatorData.value.reduce((acc, { data }) => {
const operator = data?.operator
if (operator) {
const [a, b] = valueData.value.map(({ data }) => data?.value)
if (a && b) {
return mathFunctions[operator](a, b)
}
}
return acc
}, 0)
// Round to 2 decimal places
return Math.round(currResult * 100) / 100
})
</script>
<template>
<div class="calculation">
<template v-for="(value, i) in valueData" :key="`${value.id}-${value.data}`">
<span>
{{ value.data?.value }}
</span>
<span v-if="i !== valueData.length - 1">
{{ operatorData[0].data?.operator }}
</span>
</template>
</div>
<span> = </span>
<span class="counter" :style="{ color: result > 0 ? '#5EC697' : '#f15a16' }">
{{ result }}
</span>
<Handle
type="target"
:position="Position.Left"
:connectable="false"
:style="{ background: result > 0 ? '#5EC697' : '#f15a16' }"
/>
</template>

View File

@@ -0,0 +1,22 @@
<script setup>
import { Handle, Position, useVueFlow } from '@vue-flow/core'
const props = defineProps(['id', 'data'])
const { updateNodeData } = useVueFlow()
function onChange(event) {
const value = Number.parseFloat(event.target.value)
if (!Number.isNaN(value)) {
updateNodeData(props.id, { value })
}
}
</script>
<template>
<label :for="`${id}-input`"> </label>
<input :id="`${id}-input`" :value="data.value" type="number" class="nodrag" @change="onChange" />
<Handle type="source" :position="Position.Right" :connectable="false" />
</template>

View File

@@ -0,0 +1,7 @@
export { default as MathApp } from './App.vue?raw'
export { default as MathElements } from './initial-elements.js?raw'
export { default as MathValueNode } from './ValueNode.vue?raw'
export { default as MathOperatorNode } from './OperatorNode.vue?raw'
export { default as MathResultNode } from './ResultNode.vue?raw'
export { default as MathIcon } from './Icon.vue?raw'
export { default as MathCSS } from './style.css?inline'

View File

@@ -0,0 +1,31 @@
export const initialNodes = [
{
id: '1',
position: { x: 0, y: 0 },
type: 'value',
data: { value: 10 },
},
{
id: '2',
position: { x: 0, y: 100 },
type: 'value',
data: { value: 30 },
},
{
id: '3',
position: { x: 300, y: 35 },
type: 'operator',
data: { operator: '+' },
},
{
id: '4',
position: { x: 650, y: 15 },
type: 'result',
},
]
export const initialEdges = [
{ id: 'e1-3', source: '1', target: '3', animated: true, targetHandle: 'target-a' },
{ id: 'e2-3', source: '2', target: '3', animated: true, targetHandle: 'target-b' },
{ id: 'e3-4', source: '3', target: '4', animated: true },
]

View File

@@ -0,0 +1,123 @@
.math-flow {
background-color: #edf2f7;
height: 100%;
width: 100%;
}
.vue-flow__handle {
height: 24px;
width: 10px;
background: #aaa;
border-radius: 4px
}
.vue-flow__edges path {
stroke-width: 3;
}
.vue-flow__node-value {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: #f3f4f6;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.vue-flow__node-value.selected {
box-shadow: 0 0 0 2px #ec4899;
}
.vue-flow__node-value input {
flex: 1;
padding: 8px;
border: none;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.vue-flow__node-value input:focus {
outline: none;
}
.vue-flow__node-value .vue-flow__handle {
background-color: #ec4899;
}
.vue-flow__node-operator {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px 24px;
background-color: #f3f4f6;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.vue-flow__node-operator.selected {
box-shadow: 0 0 0 2px #0EA5E9;
}
.vue-flow__node-operator .buttons {
display: flex;
gap: 8px;
}
.vue-flow__node-operator button {
border: none;
cursor: pointer;
background-color: #4a5568;
border-radius: 8px;
color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
width: 40px;
height: 40px;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.vue-flow__node-operator button:hover {
background-color: #0EA5E9;
transition: background-color 0.2s;
}
.vue-flow__node-operator button.selected {
background-color: #0EA5E9;
}
.vue-flow__node-operator .vue-flow__handle[data-handleid="target-a"] {
top: 25%;
}
.vue-flow__node-operator .vue-flow__handle[data-handleid="target-b"] {
top: 75%;
}
.vue-flow__node-operator .vue-flow__handle {
background-color: #0EA5E9;
}
.vue-flow__node-result {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px 24px;
background-color: #f3f4f6;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.vue-flow__node-result.selected {
box-shadow: 0 0 0 2px #5EC697;
}
.vue-flow__node-result .calculation {
display: flex;
gap: 8px;
font-size: 24px;
}

View File

@@ -206,6 +206,7 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
{ text: 'Drag & Drop', link: '/examples/dnd' },
{ text: 'Interactions', link: '/examples/interaction' },
{ text: 'Save & Restore', link: '/examples/save' },
{ text: 'Math Operation Flow', link: '/examples/math' },
{ text: 'Screenshot', link: '/examples/screenshot' },
{ text: 'Node Visibility', link: '/examples/hidden' },
{ text: 'Node Intersections', link: '/examples/intersection' },

View File

@@ -0,0 +1,9 @@
# Math Operation
This example demonstrates how to use the different composables like `useHandleConnections` or `useNodesData` to create a data flow between nodes.
In this example we use nodes to create a simple math operation, where the user can input two numbers and select an operation to perform.
<div class="mt-6">
<Repl example="math"></Repl>
</div>

View File

@@ -1,4 +1,3 @@
# Stress
Vue Flow is built to be fast but there are limits.
@@ -6,9 +5,5 @@ Vue Flow is built to be fast but there are limits.
Try out how many nodes you can get to render before your browser crashes.
<div class="mt-6">
<ClientOnly>
<Suspense>
<Repl example="stress"></Repl>
</Suspense>
</ClientOnly>
<Repl example="stress"></Repl>
</div>

View File

@@ -4,9 +4,5 @@
Teleport your nodes to another dom element using Vue 3 [`Teleport`](https://vuejs.org/guide/built-ins/teleport.html).
<div class="mt-6">
<ClientOnly>
<Suspense>
<Repl example="teleport"></Repl>
</Suspense>
</ClientOnly>
<Repl example="teleport"></Repl>
</div>