드래그앤드롭으로 노드 간 간선 생성
🤔 초기 구상
- 노드 생성 로직 구현한 것에서 드래그 앤 드롭 시 커서 위치를 계속 추적하고 있다.
- 그럼 DragEnd일 때 드롭 좌표에 따라 분기처리만 하면 되지 않을까?
간단하게는 총 2개의 방법이 떠오른다.
[1] drop 좌표를 계산해서, space에 그려진 노드들의 범위들을 계산 후 순회하여 겹치는 것이 있는지 식별하기
[2] Konva의 데모에 있는 hit detection을 활용해서 노드를 사각형으로 감싸고, haveIntersection 메소드 활용하기
직관적으로 생각하기에는 [1]이 탐색하고 연산하는 과정이 많을 것 같은데 사실 [2]도 내부 구현을 잘 모르겠다. 구현 난이도가 간단한 것은 [2]로 예상되므로, [2]로 먼저 시도해보자!
🐝 Have Intersection 사용하기
1
2
3
4
5
if (haveIntersection(group.getClientRect(), targetRect)) {
group.findOne('.fillShape').fill('red');
} else {
group.findOne('.fillShape').fill('grey');
}
Client Rect가 뭘까?
Konva의 공식문서에서는 getClientRect()메소드를 아래와 같이 설명한다.
Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). The purpose of the method is similar to getBoundingClientRect API of the DOM.
특정 Konva 노드가 차지하는 클라이언트 영역의 크기와 위치를 반환한다. 뷰포트를 기준으로 측정하여, 스타일( stroke, shadow, transform 등)을 포함한 실제 영역을 계산해준다. 따라서, 드래고 되고 있는 GooeyNode의 좌표 = dragPosition, 화면에 렌더링된 기존 노드의 좌표 = { x: node.x, y: node.y }를 활용하면 쉽게 구할 수 있을 것 같다.
아래의 붉은 테두리가 getClientRect를 통해 표시된 Konva 노드의 크기이고, collision이 감지되었을 때 해당 도형을 붉은 색으로 표시하는 예시다.
https://konvajs.org/docs/sandbox/Collision_Detection.html
HoneyFlow에 적용하면 다음과 같다.
useDragNode커스텀훅에 있는 dragEnd 핸들러 함수에 다음의 로직을 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function handleDragEnd() {
// 드래그 상태에서 아래의 두 정보를 추출
const { 드래그시작노드, 드래그중인 좌표 } = dragState;
// 렌더링된 node를 순회하여 haveIntersection 메소드로 테스트를 진행
const overlapNode = nodes.find ( (node) => {
const result = haveIntersection({ 드래그시작노드의 getClientRect정보 },
{ 드래그중인 gooey노드의 getClientRect정보 });
return result;
}
// 겹치는 노드가 있는지에 따라 로직 수행
overlapNode가 있으면 -> dropPosition null 처리 / 두 노드의 간선을 생성
없으면 -> 새로운 노드 생성 로직
}
이렇게 하면 1차적으로는 잘 작동하는데, 엣지케이스와 영상에서도 보이는 버그를 생각해봐야 한다.
- 자기 자신으로 재귀적으로 드롭했을 때 간선이 추가되는지?
- 이미 간선이 표시되어있는 두 노드 사이를 드래그&드롭했을 때, 드래그시작-드롭한 순서가 달라지면 새로운 간선으로 간주됨
- (+) 다른 노드와 일정 범위에서 가까워지면 Gooey Connection 표시하는 것이 본래 기획에 더 가까움
🐝 Bug 잡기…ㅠㅠ
위의 리스팅한 이슈들 중 우선순위를 선정해보자.
- 기존 노드 사이의 간선 생성이 바로 안되는 것 ✅
- 이미 간선이 있는 두 노드 사이에 새로운 간선이 생성되는 것 ✅
- 자기 자신으로 드롭했을 때의 간선이 생성되는 것 ✅
기존 노드 사이의 간선 생성이 바로 안되는 것 ✅
계속 시도하다보니 원인을 찾았다. 드롭할 때 한 개의 원만 겹치는 게 아니라… 다수의 원이 겹칠 수 있기 때문에 발생하는 문제다.
- 대응책들을 생각해보자.
(1) 드롭 위치가 다수의 노드와 중첩되면 노드 생성 막아버리기
(2) 더 많은 영역이 겹친 노드와 연결해주기 (얼마나 겹쳤는지 비교할 수 있어야 함, 노드의 중심점 거리도 비교할 수 있음)
어떤 대응책을 고르더라도, 사용자에게 보여줄 표지가 있어야 할 것 같다. 지금 중첩되고 있는 노드들을 표시하거나, 더 가까운 (드롭 시 연결될 예정인) 노드를 표시하는 것이다.
사용자 경험에서는 어떤 것이 더 좋을까? Space에 노드가 많아지면, 중첩될 일이 많아질텐데 생성 자체를 막아버리면 간선 생성이 너무 어려워질 것 같다. 반면, 새로운 노드를 생성하고 싶을 때 간선 생성이 너무 잘되면 빈 공간에 드롭하여 노드를 생성하는 것도 어려울 수도 있다.
(아니다,, 화면조작으로 Space 화면을 축소해서 노드를 조작하면 괜찮을까?)
그럼 무조건 노드가 중첩되는 것뿐만 아니라, 드래그하고 있는 커서 위치와 각 노드의 중심점의 거리를 필수적으로 구해야 할 수도 있다. [두 노드의 중첩 + 일정 범위 내에 들어야만 간선 생성]으로 간선 생성 조건을 구체화하는 것이다.
- 드래그되고 있는 위치에 중첩되고 있는 노드 표시해주기.
일단 간선 생성이 트리거되는 조건은 팀원들의 조언을 구해서 다시 생각해보고, 현재 중첩되고 있는 노드를 표시해주는 로직부터 구현해보자.
- 일단 nodes데이터, Node 컴포넌트는 모두 SpaceView 컴포넌트에서 다뤄진다.
- 다른 커스텀훅에서 관리하여 node 데이터 상태를 반환하고, 그 상태를 useDragNode 커스텀훅에서 받아서 사용한다.
- 그럼 반대로 useDragNode에 정의되어있는 드롭 핸들러 함수에서 노드의 스타일링을 바꾸려면 어떻게 해야 할까?
nodes 데이터 상태를 다루는 함수를 받아서 활용한다고 하더라도 … 데이터 안에는 스타일링 관련된 내용을 다루지 않는다. 당장 떠오르는 최선의 방식은 드래그 중인 특정 상황일 때만 GooeyNode 컴포넌트를 렌더링하는 것처럼, 간선 생성하고자 하는 중첩된 노드가 있을 때만 indicator 컴포넌트를 렌더링해주는 것이다.
이미 간선이 있는 두 노드 사이에 새로운 간선이 생성되는 것 ✅
이건 간선이 생성되는 로직에서 검사하는 것이 자연스럽다고 생각했다. 일단 dragEnd핸들러에서는 그대로 createEdge함수를 실행한다.
대신 createEdge함수에 이미 포함된 간선인지 검사하는 과정을 추가한다. 이때 (from: A, to: B), (from: B, to: A) 두 가지 경우는 두 노드의 순서가 달라도 같은 간선을 의미하므로 순서가 반대일 때도 체크한다.
1
2
3
4
5
6
7
const isSameEdgeExist = edges.some(
(edge) =>
(edge.from.id === toNode.id && edge.to.id === fromNode.id) ||
(edge.from.id === fromNode.id && edge.to.id === toNode.id),
);
if (isSameEdgeExist) return;
자기 자신으로 드롭했을 때의 간선이 생성되는 것 ✅
포개진 노드의 Id가 드래그가 시작된 노드 Id와 일치하지 않을 때만 간선이 생성되도록 변경하면 된다.
1
2
3
4
if (overlapNode && **overlapNode.id !== startNode.id**) {
setDropPosition(null);
spaceActions.createEdge(startNode, overlapNode);
}



