4. edge connection
ROS2에 대해 알아보자
4. edge connection
Edge Connection
- 생성된 노드간 엣지를 연결하는데 사용되며, Vue Flow에서 사용되는 주요 기능들은 다음과 같음
Getter
- getSelectedElements() - returns all currently selected elements
Action
- addEdges() - edge parse 후 edge state(node/handle)에 추가하여 노드간 edge 커넥션을 만듦
- fitView() - Fit the viewport around visible nodes
- toObject() - return an object of graph values (elements, viewport transform) for storage and re-loading a graph
- fromObject() - load graph from export obj
- applyNodeChanges() - applies default node change handler
- updateNodeData() - vue flow 내에서 해당하는 노드 id에 data 값을 업데이트
- useNodeConnections() - 노드에 연결된 모든 에지를 검색하는 후크이며, 핸들 유형 및 ID로 필터링도 수행 가능
- useNodesData() - 하나 또는 여러 노드의 데이터를 받아오기 위해 사용
코드 구성
FlowCanvas.vue
- 드래그앤 드롭은 vue-flow에 emit 이벤트를 받아와 처리(@dragover/@dragLeave)
- 노드간 연결은 vue-flow에 onConnect emit 이벤트를 받아와 addEdge Action Call로 처리
- Json Save/Load/Print는 toObject/fromObject를 활용하여 로컬 스토리지에 저장 및 불러오기 수행
- Delete는 keydown 이벤트와 getSelectedElements로 해당하는 아이디를 받아와 해당하는 노드 업데이트 수행(applyNodeChanges)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<script lang="ts" setup>
import { markRaw, ref, nextTick } from 'vue'
import { MiniMap } from '@vue-flow/minimap'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import DropzoneBackground from './DropzoneBackground.vue'
import FlowSidebar from './FlowSidebar.vue'
import useDragAndDrop from './useDnD'
import { CustomTypesDefine, type CustomNode, nodeStroke } from './customnodes/CustomNodeTypes.ts'
import VariableNode from './customnodes/VariableNode.vue'
import OperatorNode from './customnodes/OperatorNode.vue'
import ResultNode from './customnodes/ResultNode.vue'
import { useLayout } from './layout/useLayout'
import { onSave, onRestore, getFlowObject } from './vue-flow-file-control/SaveRestoreControl.ts'
const { addEdges, fitView, toObject, fromObject, getSelectedElements, applyNodeChanges } =
useVueFlow()
const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()
const { layout } = useLayout()
const nodes = ref([])
const edges = ref([])
const customNodeTypes: CustomNode = {
[CustomTypesDefine.VARIABLE_NODE]: markRaw(VariableNode),
[CustomTypesDefine.OPERATOR_NODE]: markRaw(OperatorNode),
[CustomTypesDefine.RESULT_NODE]: markRaw(ResultNode),
}
async function layoutGraph(direction) {
await stop()
nodes.value = layout(nodes.value, edges.value, direction)
nextTick(() => {
fitView()
})
}
const onConnect = (connection) => {
addEdges(connection)
}
const onSaveClicked = () => {
const getFlowObject = toObject()
onSave(getFlowObject)
}
const onLoadClicked = () => {
const savedObject = onRestore()
if (savedObject) {
fromObject(savedObject)
}
}
const onPrintJsonClicked = () => {
console.log(getFlowObject())
console.log(toObject())
console.log(typeof toObject())
}
const handleKeyDown = (e) => {
console.log(e)
console.log(getSelectedElements.value)
if (e.key === 'Delete') {
applyNodeChanges([
{
id: getSelectedElements.value[0].id,
type: 'remove',
},
])
}
}
</script>
<template>
<div class="d-flex flex-column">
<div class="d-flex flex-row">
<div style="width: 96vw">
<button class="button-style" @click="onSaveClicked">Save</button>
<button class="button-style" @click="onLoadClicked">LOAD</button>
<button class="button-style" @click="onPrintJsonClicked">Print Json</button>
<button style="float: right" class="button-style" @click="layoutGraph('LR')">
가로 배치
</button>
<button style="float: right" class="button-style" @click="layoutGraph('TB')">
새로 배치
</button>
</div>
</div>
<div class="dnd-flow" @drop="onDrop">
<VueFlow
v-model:nodes="nodes"
v-model:edges="edges"
:node-types="customNodeTypes"
@dragover="onDragOver"
@dragleave="onDragLeave"
@connect="onConnect"
:min-zoom="0.2"
:default-zoom="1.5"
:max-zoom="4"
@keydown="handleKeyDown"
>
<DropzoneBackground
:style="{
backgroundColor: isDragOver ? 'black' : 'transparent',
transition: 'background-color 0.2s ease',
}"
>
<p v-if="isDragOver">Drop here</p>
</DropzoneBackground>
<MiniMap class="minimap" :node-color="nodeStroke" />
</VueFlow>
<FlowSidebar />
</div>
</div>
</template>
VariableNode.vue
- 인풋노드에서 인풋값을 받고 노드 값 업데이트(updateNodeData)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script lang="ts" setup>
import { computed } from 'vue'
import { Handle, Position, useVueFlow } from '@vue-flow/core'
const props = defineProps(['id', 'data'])
const { updateNodeData } = useVueFlow()
const value = computed({
get: () => props.data.value || 0,
set: (value) => updateNodeData(props.id, { value }),
})
</script>
<template>
<div class="vue-flow__node-value">
<input :id="`${id}-input`" v-model="value" type="number" />
<Handle type="source" :position="Position.Bottom" :connectable="true" />
</div>
</template>
OperatorNode.vue
-
현재 노드에서 target으로된 핸들 노드의 값들을 받아오고,
- useNodesData = 필터링되어 받아온 노드데이터들의 id로 각 노드의 정보를 배열 형태로 가져옴
- useNodeConnections = 핸들타입 target으로 연결된 노드 리스트들을 필터링해서 받아옴
-
각 노드 핸들에 인풋 데이터가 아닌 OperatorNode로 연결된 경우 인풋 값이 아닌 계산 결과 값(result)을 받아와 업데이트
- updateNode = 핸들에 들어온 값들이 VariableNode의 입력값인지, OperatorNode값인지에 따라 계산 결과 값 처리
- updateNodeData = 계산 결과값을 해당하는 노드에 업데이트 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<script lang="ts" setup>
import { Handle, Position, useVueFlow, useNodeConnections, useNodesData } from '@vue-flow/core'
import { ref, watch } from 'vue'
import Icon from './OperatorIcon.vue'
const props = defineProps(['id', 'data'])
const operators = ['+', '-', '*', '/']
const mathFunctions = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b,
}
const selectedOperator = ref('+')
const { updateNodeData } = useVueFlow()
const sourceConnections = useNodeConnections({
handleType: 'target',
})
const valueData = useNodesData(() => sourceConnections.value.map((connection) => connection.source))
const updateNode = (operator) => {
if (valueData.value.length < 2) return
const [a, b] = valueData.value.map(({ data }) => data?.value)
if (a === undefined && b === undefined) {
updateNodeData(props.id, {
result: mathFunctions[operator](
valueData.value[0].data?.result,
valueData.value[1].data?.result,
),
operator: operator,
})
} else if (a === undefined) {
updateNodeData(props.id, {
result: mathFunctions[operator](valueData.value[0].data?.result, b),
operator: operator,
})
} else if (b === undefined) {
updateNodeData(props.id, {
result: mathFunctions[operator](a, valueData.value[1].data?.result),
operator: operator,
})
} else {
updateNodeData(props.id, {
result: mathFunctions[operator](a, b),
operator: operator,
})
}
}
const onOperatorButtonClicked = (operator) => {
selectedOperator.value = operator
updateNode(operator)
}
watch(
() => [valueData.value],
async () => {
updateNode(selectedOperator.value)
},
)
</script>
<template>
<div class="vue-flow__node-operator buttons">
<button
v-for="operator of operators"
:key="`${id}-${operator}-operator`"
:class="{ selected: data.operator === operator }"
@click="onOperatorButtonClicked(operator)"
>
<Icon :name="operator" />
</button>
</div>
<Handle id="input-1" type="target" :position="Position.Top" :connectable="true" />
<Handle id="input-2" type="target" :position="Position.Top" :connectable="true" />
<Handle type="source" :position="Position.Bottom" :connectable="true" />
</template>
ResultNode.vue
- VariableNode/OperatorNode에서 업데이트(updateNodeData)해주는 input Value 값과 연산자 값들을 받아와 식 및 결과값 출력
- operatorData =결과노드의 연결된 연산자 노드의 값을 받아옴
- valueData = 결과 노드에 연결된 연산자 노드의 target 핸들에 연결된 노드값들을 받아옴
- valueData에서 target 핸들에 연결된 노드가 variablenode가 아닌 operatornode면 해당 계산 결과값을 가져와 result 출력
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<script lang="ts" setup>
import { computed } from 'vue'
import { Handle, Position, useNodeConnections, useNodesData } from '@vue-flow/core'
const sourceConnections = useNodeConnections({
handleType: 'target',
})
const operatorSourceConnections = useNodeConnections({
handleType: 'target',
nodeId: () => 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(() => {
return operatorData.value[0]?.data?.result
})
</script>
<template>
<div class>
<template v-for="(value, i) in valueData" :key="`${value.id}-${value.data}`">
<span v-if="value.id.startsWith('var_')">
</span>
<span v-else>
</span>
<span v-if="i !== valueData.length - 1">
</span>
</template>
</div>
<span> = </span>
<span class="result" :style="{ color: result > 0 ? '#5EC697' : '#f15a16' }">
</span>
<Handle
type="target"
:position="Position.Top"
:connectable="true"
:style="{ background: result > 0 ? '#5EC697' : '#f15a16' }"
/>
</template>
This post is licensed under
CC BY 4.0
by the author.