JavaScript
SVG
es6
React

SVGで円同士を線で結ぶ(円周上の点から)

SVGを書くときに、円(circle)同士結ぶ線(path)を引きたくなりました。この時、円の中心座標、半径はわかっているものとします。


やりたいこと

スクリーンショット 2019-04-16 4.37.30.png

こうではなく、

スクリーンショット 2019-04-16 4.34.35.png

こうしたい!


変数設定

円の情報はcirclesで保持します。簡単のため、すべての円の半径は等しいとします。

const circles = [{x:10, y:10}, {x:20, y:20}, {x:30, y:0}];

const r = 3;

縁を結ぶ順番は、circlesのインデックスをpointsで保持するものとします。図の例だと、

const points = [0, 1, 2];

(単純でいい例ではないですね・・・)

svgによる線の描画はpathを用いて

<path d=*** style={{ ... }} ></path>;

と書けます。d=***で線のパスを定義します。例えば、点(0,0)と点(10,10)を結ぶpathの場合、d=M0 0L10 10となります。

点(0,0),(10,10),(20,20)を結ぶ場合、d=M0 0L10 10L20 20となり始点はMから始まり、その後の点はLを使います。

今回はこのd=***を計算する関数をcreatePathString(points, circles)とします。


単純に結ぶ

円の中心座標はわかっているので、この座標を始点としてpathを引けばいいです。

const createPathString = (points, circles) => {

return points.reduce((prev, curr, index) => {
const [x, y] = [circles[curr].x, circles[curr].y];
const str = (index === 0 ? 'M' : 'L') + x + ' ' + y;
return prev + str;
}, '');
}

これによって生成される文字列はM10 10L20 20L30 0となります。これは単純に中心を結んでいるだけですね。


円周上の点から結びたい

しかし、図のように円周上の点から結びたい状況になりました。また、円の内部は透明なので、円を最前面に持ってきて重ねて消す、ということもできません。

もしかしたらもっと簡単な解決法があるかもしれませんが、今回は円周上の点を計算して求めて、その点を結ぶという手法で実現していきたいと思います。

j.png

非常に雑な図ですが、2点間を結んでできる三角形の内角のcos,sinを求めればいいです。

円周上の二点を求める関数をcalcPointsOnCircle(x1, y1, x2, y2, r)とします。

calcPointsOnCircle = (x1, y1, x2, y2, r) => {

const c = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
const cos = (x2 - x1) / c;
const sin = (y2 - y1) / c;
const [_x1, _y1] = [r * cos, r * sin];
const [_x2, _y2] = [(c - r) * cos, (c - r) * sin];
return [x1 + _x1, y1 + _y1, x1 + _x2, y1 + _y2];
}

これを用いて

const createPathString = (points, circles, r) => {

return points.reduce((prev, curr, i, arr) => {
if (i + 1 === arr.length) return prev;
const [x1, y1, x2, y2] = this.calcPointsOnCircle(circles[curr].x, circles[curr].y, circles[arr[i + 1]].x, circles[arr[i + 1]].y, r)
return [...prev, `M${x1} ${y1}L${x2} ${y2}`];
}, []);
}

無事に2本のパスが生成されました

["M12.121320343559642 12.121320343559642L17.878679656440358 17.878679656440358", 

"M21.341640786499873 17.316718427000254L28.658359213500127 2.6832815729997463"]