5

与えられた2つの円の間に矢印付きの線を描くにはどうすればよいですか:

  1. 円の中心の位置
  2. 円の半径

マーカーのsvg オブジェクトを使用しています。

円の「中心」に矢印を描くと、矢印は見えなくなります。矢印を後ろに移動しすぎると、線が透けて見え、矢印の先のとがった端が非表示になります (ここでは、見やすくするために誇張しています)。 矢印が戻ってきた

リクエストに応じて、これが私のコードの関連ビットです(ライブスクリプト内):

# Draw an arrow to use for lines
svg.append("svg:defs")
 .append("svg:marker")
  .attr("id", "arrow")
  .attr("viewBox", "0 0 10 10")
  .attr("refX", 27)
  .attr("refY", 5)
  .attr("markerUnits", "strokeWidth")
  .attr("markerWidth", 8)
  .attr("markerHeight", 6)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M 0 0 L 10 5 L 0 10 z")

svg.append("line")
 .attr "x1" 5 
 .attr "x2" 50 
 .attr "y1" 5 
 .attr "y2" 50
 .style "stroke" "black"
 .attr "stroke-width" 2
 .attr "marker-end" "url(\#arrow)"

または、ここに実際の例の JSFiddle があります (矢印は正しく見えるように「そわそわ」していることに注意してください): http://jsfiddle.net/yeQS2/

4

4 に答える 4

8

私は同じ問題を抱えていましたが、これが私がそれを解決した方法です。元のフィドルに加えられた変更:

に変更.attr("refX", 27).attr("refX", 0)ます。これにより、矢印が線の端を超えて描画されます。

次のコードを「tick」に追加して、三角法を使用して、矢印を考慮して、線の適切な終了位置を計算します。

var arrowheadLength = 8, // from markerWidth
    nodeRadius = 10;
link.each(function(d) {
  var x1 = d.source.x,
      y1 = d.source.y,
      x2 = d.target.x,
      y2 = d.target.y,
      angle = Math.atan2(y2 - y1, x2 - x1);
  d.targetX = x2 - Math.cos(angle) * (nodeRadius + arrowheadLength);
  d.targetY = y2 - Math.sin(angle) * (nodeRadius + arrowheadLength);
});

計算された targetX および targetY リンク プロパティを使用します。

.attr("x2", function(d){
  return d.targetX;
}).attr("y2", function(d){
  return d.targetY;
})

これが更新された fiddleです。

于 2014-06-19T18:27:55.517 に答える
4

わかりましたので、ベクトル計算でこれを実装してみようと思いました。よりきれいで、結果は再利用可能です。

いくつかの説明:

  • 「ベクトル」は単純に 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を円から遠ざけることです。そのために、ベクトル演算を使用して

  1. 位置付けられたベクトルを解放します (リンクは 2 つの座標で構成され、固定されていない単一のベクトルが必要です)
  2. 自由ベクトルの単位ベクトルを計算する
  3. それが得られたら、それを掛けることができnodeRadiusます。この新しいベクトルは、リンクと同じ方向で、ノードの中心とその境界の間の距離を表します。
  4. ベクトルをソース座標に追加すると、これらの新しい座標は円の端になります。

では、次の関数を使用してこれを行います。

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)。

于 2014-01-03T17:39:44.263 に答える
2

@andsens が言ったように、単純なベクトル操作を行っています。

適切なライブラリにラップすると、これははるかにきれいに実行できます。たとえば、私はすばらしいSylvester行列およびベクトル ライブラリを使用しています。

基本的に計算しているのは次のとおりです。

V 下付きエッジは、開き括弧係数 V から R 閉じ括弧を単位ベクトル V に掛けた値に等しい

ここで、 vはターゲットの中心へのベクトルで、vは半径rのターゲットのエッジへのベクトルをエッジします。

簡単にできること:

// Assume source and target both have x and y properties
// Assume target has radius property
function path2TargetEdge(source, target){

  // V is the vector from the source to the target's center
  var V = $V([target.x-source.x, target.y-source.y]);

  // Vt is the vector from the source to the edge of the target
  var Vt = V.toUnitVector().multiply(V.modulus() - target.radius);

  return {x: Vt.e(1), y: Vt.e(2) }; // Vectors are 1-indexed
}
于 2014-03-04T02:08:09.047 に答える