わかりましたので、ベクトル計算でこれを実装してみようと思いました。よりきれいで、結果は再利用可能です。
いくつかの説明:
- 「ベクトル」は単純に 2 つの数値 (x と y) です。
- 「座標」はベクトルと同じ構造で、私たちにとっては意味が違うだけです。ただし、同じ計算を実行できます。
- 「配置されたベクトル」は 2 つのベクトル (ソースと宛先など) です。
- 最初のベクトルを 2 番目のベクトルから差し引くことで、配置されたベクトルを「解放」できます (座標系に固定されていない新しいベクトルを取得します)。
- ベクトルの「長さ」は、ピタゴラスの定理(ノルムとも呼ばれます)を使用して計算されます。
- 「ベクトル加算」とは、単純に 2 つ以上のベクトルの x と y を加算して、新しいベクトルを作成することです。
- 「スカラー倍算」と割り算は、x と y をスカラーで割ることによって行われます。
- 「単位ベクトル」は、ベクトルをその長さで割ったものです
これを動的に(「ティックごとに」)機能させたいと仮定すると、最初のリンク調整は次のようになります(私はcoffeescriptを使用しています):
links.attr('x1', ({source,target}) -> source.x)
.attr('y1', ({source,target}) -> source.y)
.attr('x2', ({source,target}) -> target.x)
.attr('y2', ({source,target}) -> target.y)
やりたいことは、ソースとターゲットnodeRadius
を円から遠ざけることです。そのために、ベクトル演算を使用して
- 位置付けられたベクトルを解放します (リンクは 2 つの座標で構成され、固定されていない単一のベクトルが必要です)
- 自由ベクトルの単位ベクトルを計算する
- それが得られたら、それを掛けることができ
nodeRadius
ます。この新しいベクトルは、リンクと同じ方向で、ノードの中心とその境界の間の距離を表します。
- ベクトルをソース座標に追加すると、これらの新しい座標は円の端になります。
では、次の関数を使用してこれを行います。
length = ({x,y}) -> Math.sqrt(x*x + y*y)
sum = ({x:x1,y:y1}, {x:x2,y:y2}) -> {x:x1+x2, y:y1+y2}
diff = ({x:x1,y:y1}, {x:x2,y:y2}) -> {x:x1-x2, y:y1-y2}
prod = ({x,y}, scalar) -> {x:x*scalar, y:y*scalar}
div = ({x,y}, scalar) -> {x:x/scalar, y:y/scalar}
unit = (vector) -> div(vector, length(vector))
scale = (vector, scalar) -> prod(unit(vector), scalar)
free = ([coord1, coord2]) -> diff(coord2, coord1)
これは少し圧倒されるように見えるかもしれませんが、非常にコンパクトです。これは、coffeescript を使用すると、メソッド シグネチャ内で物事を直接分解できるため、非常に便利です! ご覧のとおり、 という別の関数がありますscale
。手順 2. と 3. を組み合わせる便利な機能です。
次に、リンク ソースの新しい x 座標を設定してみましょう。覚えておいてください: 座標はnodeRadius
、円の内側ではなく境界から始まるように、 だけ移動する必要があります。
(d) ->
# Step 1
freed = free(d)
# Step 2
unit = unit(freed)
# Step 3
scaled = prod(unit, nodeRadius)
# Step 2+3 would be scale(freed, nodeRadius)
# Step 4, coords are pretty much just vectors,
# so we just use the sum() function to move the source coords
coords = sum(d.source, scaled)
return coords.x
それに何もない!そのすべてをtick()
関数に入れると、次のようになります。
links.attr('x1', ({source,target}) -> sum(source, scale(free([source,target]), nodeRadius)).x)
.attr('y1', ({source,target}) -> sum(source, scale(free([source,target]), nodeRadius)).y)
.attr('x2', ({source,target}) -> diff(target, scale(free([source,target]), nodeRadius)).x)
.attr('y2', ({source,target}) -> diff(target, scale(free([source,target]), nodeRadius)).y)
ああ、ターゲット座標から減算することを忘れないでください。そうしないと、線が再び長くなります (つまり、 だけ移動しますnodeRadius
)。