각도에 따른 SVG Element 생성 및 회전

Dzeko·2023년 8월 21일
0

개발일지

목록 보기
99/112
post-thumbnail

지난 버전에 들어갔던 Edge label이다.
라인에 라벨표시를 하는 것인데, port의 이름이 표시된다.
단순하게 라인의 정가운데 좌표를 구해서 박아줬었는데,

이번 버전에서 새로운 요구사항이 들어왔다.
1. 라벨이 라인의 방향 & 각도에 따라서 같이 회전해야 한다.
2. 항상 라인의 상단에 위치할 것.
3. 라인의 길이 보다 text가 길 경우, 라인의 길이 만큼만 출력한다.

라인이 생성되는 곳에 createSvgLabel method를 만들었다.

우선 return할 svg element를 만들고,

라인을 형성하는 x,y 좌표를 가진 꼭지점 2개를 파라미터로 받는다.

이를 가지고 라인의 길이를 직접 구해야 한다.

const createSVGLabel = (points) => {
   	const svg = document.createElementNS(mxConstants.NS_SVG, 'g');
	const text = document.createElementNS("http://www.w3.org/2000/svg", "text");

	const xValues = points.map(point => point.x);
	const yValues = points.map(point => point.y);

	const minX = Math.min(...xValues);
	const maxX = Math.max(...xValues);
	const minY = Math.min(...yValues);
	const maxY = Math.max(...yValues);
  
  	// 라인의 길이
    const edgeLength = Math.sqrt(Math.pow(maxX - minX, 2) + Math.pow(maxY - minY, 2));
}

라인의 각도에 따라 회전시켜야 하기 때문에 라인의 각도를 구한다.

// 각도 구하기;
const createSVGLabel = (points) => {
  .
  .
  .
  const dx = points[1].x - points[0].x;
  const dy = points[1].y - points[0].y;
  const radians = Math.atan2(dy, dx);
  const angle = radians * (180 / Math.PI);
}

각도는 3시방향을 기준으로 0도, 90도, -90도, 180도(-180도)로 네 구역을 나누고, 각각 계산을 용이하게 하기 위해 newAngle을 만들어줬다.
그리고, text가 중앙에서 시작되기 때문에 text의 길이만큼 앞으로 땡겨줘야 하는데, 이는 각도마다 다르게 적용해야 했으므로 이 안에서 해준다.

이 때, text의 길이는 단순히 text의 length로 하게 되면 text에 따라 오차가 많이 난다.
영 대/소문자, 한글 등 문자에 따라 svg에 그려지는 너비가 다르기 때문이다. 때문에, 임의의 html element를 만들어 그 element의 너비를 구했다.

const getHtmlWidthByText = (text) => {
  const tempElement = document.createElement("span");
  tempElement.style.fontSize = "12px";
  tempElement.style.fontFamily = "Open Sans, sans-serif";
  tempElement.innerHTML = text;
  document.body.appendChild(tempElement);
  const width = tempElement.offsetWidth;
  document.body.removeChild(tempElement);
  return width;
}
const createSVGLabel = (points) => {
  .
  .
  .
  const textWidth = getHtmlWidthByText(this.portState);
  
  if (angle > 0 && angle < 90) {
      const newAngle = angle;
      const moveByTextWidthX = textWidth / 2 - newAngle * textWidth / 180;
      const moveByTextWidthY = newAngle * textWidth / 180;
      xLocation = (minX + maxX) / 2 - moveByTextWidthX;
      yLocation = (minY + maxY) / 2 - moveByTextWidthY;
    }
    else if (angle >= 90 && angle <= 180) {
      const newAngle = angle - 90;
      const moveByTextWidthX = newAngle * textWidth / 180;
      const moveByTextWidthY = textWidth / 2 - newAngle * textWidth / 180;
      xLocation = (minX + maxX) / 2 - moveByTextWidthX;
      yLocation = (minY + maxY) / 2 + moveByTextWidthY;
    }
    else if (angle <= 0 && angle > -90) {
      const newAngle = - angle;
      const moveByTextWidthX = textWidth / 2 - newAngle * textWidth / 180;
      const moveByTextWidthY = newAngle * textWidth / 180;
      xLocation = (minX + maxX) / 2 - moveByTextWidthX;
      yLocation = (minY + maxY) / 2 + moveByTextWidthY;
    }
    else {
      const newAngle = - angle - 90;
      const moveByTextWidthX = newAngle * textWidth / 180;
      const moveByTextWidthY = textWidth / 2 - newAngle * textWidth / 180;
      xLocation = (minX + maxX) / 2 - moveByTextWidthX;
      yLocation = (minY + maxY) / 2 - moveByTextWidthY;
    }
}

요구사항 3번을 해결하기 위해 라인의 길이만큼 텍스트를 slice 해준다.

const sliceUnit = Math.floor(edgeLength / 10);
const labelText = this.portState.slice(0, sliceUnit);

요구사항 2번을 위해 어떤 각도에 따라 text를 뒤집어 줘야 했다.
transform, rotate로 뒤집어 준다.

const reverseAngle = (angle >= -180 && angle <= -90) || (angle >= 90 && angle <= 180) ? angle + 180 : angle;
text.setAttribute("transform", `rotate(${reverseAngle})`);

transform-origin으로 회전시켜준다!
& 마무리 return 작업

text.setAttribute("x", xLocation);
text.setAttribute("y", yLocation);
text.setAttribute("transform-origin", `${xLocation} ${yLocation}`);
const textNode = document.createTextNode(labelText);

text.appendChild(textNode);

svg.appendChild(text);

return svg;

profile
Hound on the Code

0개의 댓글