examples: add math example (#1461)
* examples: add math example * docs: add math example
This commit is contained in:
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
31
docs/examples/math/App.vue
Normal file
31
docs/examples/math/App.vue
Normal 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>
|
||||
31
docs/examples/math/Icon.vue
Normal file
31
docs/examples/math/Icon.vue
Normal 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>
|
||||
27
docs/examples/math/OperatorNode.vue
Normal file
27
docs/examples/math/OperatorNode.vue
Normal 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>
|
||||
77
docs/examples/math/ResultNode.vue
Normal file
77
docs/examples/math/ResultNode.vue
Normal 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>
|
||||
22
docs/examples/math/ValueNode.vue
Normal file
22
docs/examples/math/ValueNode.vue
Normal 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>
|
||||
7
docs/examples/math/index.ts
Normal file
7
docs/examples/math/index.ts
Normal 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'
|
||||
31
docs/examples/math/initial-elements.js
Normal file
31
docs/examples/math/initial-elements.js
Normal 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 },
|
||||
]
|
||||
123
docs/examples/math/style.css
Normal file
123
docs/examples/math/style.css
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user