mxGraph 화살표 자체개발記

Dzeko·2023년 8월 11일
0

개발일지

목록 보기
98/112
post-thumbnail

라인을 연결할 때 UX적 요소로 라인의 target, 즉 마우스로 grabbing 하는 부분에 화살표가 나왔으면 좋겠다고 하셨다.
mxGraph 문서를 검토한 결과, mxGraph에서 지원을 해주기 때문에 쉽게 넣을 수 있을 것 같았다.

 style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;

cell에 이 style만 적용시켜 주면 되었다.
하지만 왜 때문인지 도대체 왜 적용이 안되었다. 다른 건 다 되는데 이 arrow 관련 속성만 안먹었다. 무슨 짓을 해도 안되었다.
좌절하고 있던 차에, 직접 만들어 볼까? 하는 오기가 생겼다.

paintLine()이라는 마우스 동작에 따라 라인을 그리는 method가 있었다. 이 라인을 몸통으로 삼고 만들어 보기로 했다.

여기에 poins라는 파라미터가 하나 넘어온다. 뜯어보니 x,y 좌표를 가진 두 개의 꼭지점으로 보인다.

이 두 꼭지점으로 라인을 형성하는 것이 확인됐다.
배열의 첫번째 객체가 source 꼭지점이고, 두 번째 객체가 내가 사용할 target 꼭지점(마우스로 grabbing 중인 위치)이였다.
그림판으로 허접하게 그렸지만 아무튼 저 위치에 화살표를 저런식으로 만들어야 한다.
디자인을 급하게 받아왔으니, 10px 길이의 라인과 같은 색 실선으로 만들면 되었다.

먼저, 각도에 따라 화살표 모양을 그려줘야 하니 주어진 두 꼭지점으로 각도를 구했다.

const dx = pts[1].x - pts[0].x;
const dy = pts[1].y - pts[0].y;
const radians = Math.atan2(dy, dx);
const angle = radians * (180 / Math.PI);

그리고 target 꼭지점을 creteria 라는 변수로 선언하고,
피타고라스 법칙에 따라 creteria에서 10만큼 떨어진 점 위치는 x, y 각각 5√2 만큼 떨어져야 하므로, baseDistance 라는 기본값 변수를 선언했다.
화살표는 target 꼭지점을 기준으로 두 개의 선을 그리는 방식으로 할 것이다.

const criteria = pts[1];
const criteriaX = criteria.x;
const criteriaY = criteria.y;
const baseDistance = Math.sqrt(50);

그리고 화살표의 오른쪽 선을 right, 왼쪽 선을 left라 했을 때 각각의 x좌표와 y좌표를 선언했다.

let rightX = null;
let rightY = null;
let leftX = null;
let leftY = null;

각도가 0일 때를 찍어보니, 화살표가 3시 방향을 향하고 있었다.
그 상태에서 아래로 움직이면 각도가 +, 위로 움직이면 - 로 움직이는 것 까지 확인했다.

이제 화살표의 오른쪽 선, 왼쪽 선의 꼭지점을 구해야 하는데,
0도일 때(3시 방향)를 기준으로 계산을 해보았다.

3시 방향이 0도 기준이니,
right arrow의 꼭지점 좌표는 (-5√2, +5√2),
left arrow의 꼭지점 좌표는 (-5√2, -5√2)
가 된다.

이제 화살표를 위쪽으로 이동시킬 때, 즉 0도에서 90도 방향으로 움직일 때의 화살표 좌표를 계산하려면,
90도로 향해 있을 때와의 차이를 구한 후, 1도씩 나누면 되겠다고 생각했다.
90도로 향해 있으면 이렇게 된다.

right side의 꼭지점을 봤을 때, creteria 기준으로
x 좌표는 90도 동안 10√2만큼 이동했고(-5√2 => +5√2),
y 좌표는 0 만큼 이동했다. (5√2 => 5√2)
하지만 이 계산은 틀렸다.
y 좌표가 0도와 90도의 위치가 같은건 맞으나, 45도 동안 감소했다가, 다시 남은 45도 동안 그 만큼 증가한 것이다.
따라서 각도에 따라, 90도를 나눠 계산할 수도 있고, 45도 씩 나눠 계산해야만 했다.

각도는 네 구역으로 나눠 각각의 경우에 상수를 더하여 1~90도만 계산하도록 했고, 네 구역마다 감가식이 달라서 계산식을 구역에 따라 달리 해줬다.

아래의 코드가 그 결과물이다.

if ((angle >= 0 && angle <= 90) || (angle < - 90 && angle > -180)) {
  rightX = criteriaX - baseDistance - differ / 45 * angle;
  rightY = criteriaY + baseDistance - Math.sqrt(200) / 90 * angle;
  leftX = criteriaX - baseDistance + Math.sqrt(200) / 90 * angle;
  leftY = criteriaY - baseDistance - differ / 45 * angle;
  if (angle > 45) {
    rightX = criteriaX - 10 + differ / 45 * (angle - 45);
    leftY = criteriaY - 10 + differ / 45 * (angle - 45);
  }
} else if (angle < 0 && angle >= -90) {
  const newAngle = - angle;
  rightX = criteriaX - baseDistance + Math.sqrt(200) / 90 * newAngle;
  rightY = criteriaY + baseDistance - differ / 45 * angle;
  leftX = criteriaX - baseDistance + differ / 45 * angle;
  leftY = criteriaY - baseDistance + Math.sqrt(200) / 90 * newAngle;
  if (newAngle > 45) {
    rightY = criteriaY + 10 - differ / 45 * (newAngle - 45);
    leftX = criteriaX - 10 + differ / 45 * (newAngle - 45);
  }
} else if (angle < - 90 && angle > -180) {
  const newAngle = - angle - 90;
  rightX = criteriaX + baseDistance + differ / 45 * newAngle;
  rightY = criteriaY + baseDistance - Math.sqrt(200) / 90 * newAngle;
  leftX = criteriaX - baseDistance + Math.sqrt(200) / 90 * newAngle;
  leftY = criteriaY + baseDistance + differ / 45 * newAngle;
  if (newAngle > 45) {
    rightX = criteriaX + 10 - differ / 45 * (newAngle - 45);
    leftY = criteriaY + 10 - differ / 45 * (newAngle - 45);
  }
} else {// 90 ~ 180;
  const newAngle = angle - 90;
  rightX = criteriaX - baseDistance + Math.sqrt(200) / 90 * newAngle;
  rightY = criteriaY - baseDistance - differ / 45 * newAngle;
  leftX = criteriaX + baseDistance + differ / 45 * newAngle;
  leftY = criteriaY - baseDistance + Math.sqrt(200) / 90 * newAngle;
  if (newAngle > 45) {
    rightY = criteriaY - 10 + differ / 45 * (newAngle - 45);
    leftX = criteriaX + 10 - differ / 45 * (newAngle - 45);
  }
}

이제 구해진 꼭지점들로 선을 그려주니 화살표가 모든 각도에서 예쁘게 잘 따라다닌다. 성공!

profile
Hound on the Code

1개의 댓글

comment-user-thumbnail
2023년 8월 11일

이런 유용한 정보를 나눠주셔서 감사합니다.

답글 달기