SVG 벤 다이어그램 그리기

예리에르·2022년 6월 17일
2

Frontend

목록 보기
4/10
post-thumbnail

데이터 시각화를 하다보면 벤다이어그램이 쓰이는 경우가 생긴다. 그중 3개의 타겟을 가진 벤다이어그램을 그려보자.

1. 삼각형의 무게중심

사진과 같은 3개의 원을 그리기 위해서는 역정삼각형을 그려야한다. 각 꼭지점을 중심으로 같은 크기의 원을 3개 그리면된다.
밑변의 2점을 구하는건 쉽지만 꼭지점을 구하는 방법을 가만히 생각을 해봐야했다.

그래서 찾은 방법은 삼각형의 무게중심 이다.

삼각형의 세 중선은 한 점에서 만나고 점을 무게중심이라고한다. 무게중심은 세 주선을 각 꼭지점으로부터 2:1 내분한다.

이 공식을 통해 정삼각형의 높이를 구하고 무게중심을 SVG canvas으로 중심으로 고정한뒤 각 꼭지점의 좌표를 구하면 된다.

		const x1 = centerX - side / 2;
        const y1 = centerY - triangleHeight * (0.333);
        const x2 = centerX + side / 2;
        const y2 = centerY - triangleHeight * (0.333);
        const x3 = centerX;
        const y3 = centerY + triangleHeight * 0.666;

+) 위의 좌표되로 그리게 되면 서로의 중심을 지나는 벤 다이어그램이 그려지므로 임의의 거리를 더해주어 원의 거리를 이동해주었다.

2. 영역별로 그리기

2-1. 두 원의 교점 좌표 구하기

1차로 만들고 보니 고려하지 못했던 부분이 떠올랐다.

데이터가 존재하는 각 부분에 이벤트가 필요하다면 어떻게 분리하지?

현재 그려진 벤 다이어그램 하나의 원으로 그려져 교집합을 이루는 부분들을 따로 분리할 수가 없다. 결국 3개의 원으로 벤 다이어그램을 그리는것이 아니라 데이터가 존재하는 영역별로 그려야하는 것이다.

svg롤 통해 그리기위해서는 그릴려는 위치의 시작점 좌표와 끝나는 좌표가 필요하다.
결국 중심을 잡을 수있는 중심점과 원들이 교차하는 교점의 좌표를 구해야했다.

수학이론 검색하면 공통 현의 길이를 구하는 방법은 많이 나오지만 내가 딱 원하는 이론을 나오지 않아 헤매던 중 두원사이의 교점 구하기 (블로그) 포스팅을 발견하였다.

private circleTwoContactPosition = (x1: number, y1: number, x2: number, y2: number, side: number) => {
        if (x1===0) {
            return {cx1: 0, cy1: 0, cx2: 0, cy2: 0}
        }
        let xx = x2 - x1;
        let yy = y2 - y1;
        const D = Math.sqrt((xx ** 2) + (yy ** 2));//두 중심의 거리
        const T1 = Math.acos((side * side - side * side + D * D) / (2 * side * D));
        const T2 = Math.atan(yy / xx);
        const T3 = x1 + side * Math.cos(T2 + T1); //AB
        const T4 = y1 + side * Math.sin(T2 + T1); //AB
        const T5 = x1 + side * Math.cos(T2 - T1); //AB
        const T6 = y1 + side * Math.sin(T2 - T1); //AB


        return {cx1: T3, cy1: T4, cx2: T5, cy2: T6}
    }

3개의 원의 교점들의 좌표들을(6개) 구하기위해 함수를 따로 만들었다.


<circle cx={cX1} cy={cY1} r={controlSide/4} fill={"black"}/>
<circle cx={cX2} cy={cY2} r={controlSide/4} fill={"black"}/>
<circle cx={cX3} cy={cY3} r={controlSide/4} fill={"black"}/>
<circle cx={cX4} cy={cY4} r={controlSide/4} fill={"black"}/>
<circle cx={cX5} cy={cY5} r={controlSide/4} fill={"black"}/>
<circle cx={cX6} cy={cY6} r={controlSide/4} fill={"black"}/>

2-2. <path /> 을 호를 그려보자

엘립티컬 아크 를 활용했다.
A rx ry x축-회전각 큰-호-플래그 쓸기-방향-플래그 x y

a rx ry x축-회전각 큰-호-플래그 쓸기-방향-플래그 dx dy

<svg>
    <path
      d="M105,20 a20,20 0 1,1 50,25"
      fill="none" stroke="#4286f0" stroke-width="8" />
</svg>

규칙과 호 그리는 개념을 잘 활용해 그려봤다.

  					<path className={"A"} d={`M ${cX5},${cY5} A ${side},${side} 0 0,1 ${cX4},${cY4} A${side},${side} 0 0,1 ${cX2},${cY2} A${side},${side} 0 1,0 ${cX5},${cY5}`} />
                    <path className={"B"} d={`M ${cX2},${cY2} A ${side},${side} 0 0,1 ${cX6},${cY6} A${side},${side} 0 0,1 ${cX3},${cY3} A${side},${side} 0 1,0 ${cX2},${cY2}`}/>
                    <path className={"C"} d={`M${cX3},${cY3}
                    A${side},${side} 0 0,1 ${cX1},${cY1}
                    A${side},${side} 0 0,1 ${cX5},${cY5}
                    A${side},${side} 0 1,0 ${cX3},${cY3}`}/>

                    <path className={"AB"} d={`M${cX4},${cY4}
                    A${side},${side} 0 0,1 ${cX2},${cY2}
                    A${side},${side} 0 0,1 ${cX6},${cY6}
                    A${side},${side} 0 0,0 ${cX4},${cY4}`}/>
                    <path className={"BC"} d={`M${cX6},${cY6}
                    A${side},${side} 0 0,1 ${cX3},${cY3}
                    A${side},${side} 0 0,1 ${cX1},${cY1}
                    A${side},${side} 0 0,0 ${cX6},${cY6}`}/>
                    <path className={"AC"} d={`M${cX4},${cY4}
                    A${side},${side} 0 0,0 ${cX5},${cY5}
                    A${side},${side} 0 0,0 ${cX1},${cY1}
                    A${side},${side} 0 0,1 ${cX4},${cY4}`}/>
                    <path className={"ABC"} d={`M${cX4},${cY4}
                    A${side},${side} 0 0,1 ${cX6},${cY6}
                    A${side},${side} 0 0,1 ${cX1},${cY1}
                    A${side},${side} 0 0,1 ${cX4},${cY4}`}/>

이렇게 모든 부분을 각각 따로 그리게되면 위 그림처럼 hover와 같은 각각에 이벤트를 줄 수 있다.

profile
비전공 프론트엔드 개발자의 개발일기😈 ✍️

0개의 댓글