11

これはアルゴリズムの問​​題です。ソースからターゲットへの矢印接続を描画することにより、JavaScriptを使用してアイテムと他のアイテムとの関係を表示するページがあります(jsPlumbを考えてください)。各アイテムは 0 個以上の接続を持つことができます。私が抱えている課題は、最も最適な方法でコンテナーを使用して戦略的に div/circles を配置することです。

  • 最適 : 最小数の接続 (2 つの円を接続する矢印) の重なり

視覚的な例: 下の図は、コンテナ内に円をランダムに配置した、最適化されていないバージョンのディスプレイです。

ここに画像の説明を入力

上の図では、接続 (矢印) の重なりの数が不必要に多いことに注意してください。下の図は、この小さな例で接続の重複をなくすために、円をより適切な位置に配置した最適化されたソリューションの 1 つです。

ここに画像の説明を入力

アイテムを入れるコンテナのサイズは 1020x800 です。多数の円が存在する場合は常にオーバーラップが発生するため、接続のオーバーラップの量を最小限に抑えることが重要です。アルゴリズムの記事を読むのは少し難しいと思うので、これがどのように行われるかの例を期待しています:(。

4

3 に答える 3

8

アプローチ1

グラフをレイアウトするための非常に優れたアルゴリズムのクラスは、シミュレーションベースのアルゴリズムです。これらのアルゴリズムでは、物理特性を持つ物理オブジェクトであるかのようにグラフをモデル化します。

この場合、グラフのノードは互いに反発するボールであり、エッジはグラフを一緒に保つスプリングまたはゴムであると想像してください。反発力は、節点が互いに近づくほど強くなります (距離の逆二乗など)。各ばねの張力はその長さに比例します。反発力により、ノードが他のノードから可能な限り離れ、グラフがほどけます。もちろん、最良の結果を得るには、係数を少し試してみる必要があります (ただし、非常に楽しいことは保証します)。

このアプローチの主な利点は次のとおりです。

  1. コーディングが簡単 - すべての節点対節点間の力を計算し、節点位置を更新するネストされたループ
  2. 平面または非平面のすべての種類のグラフで機能します
  3. 実験するのがとても楽しい
  4. インタラクティブにする場合、たとえば、ユーザーがマウスでノードを移動できるようにする-人々を引き付け、誰もが「グラフで遊ぶ」ことを望んでいます

このアプローチの欠点は次のとおりです。

  1. 局所的なエネルギー最小値で動けなくなる可能性があります (振るか、手動で支援すると役立ちます)
  2. 非常に高速ではありません(ただし、素敵なアニメーションを作成できます)

同様の方法を使用して、ノットをレイアウト/ほどくことができます。

サンプルコード

<html>
<head>
</head>
<body>
  <canvas id="canvas" width="800" height="600" style="border:1px solid black"/>   
  <script>
    window.requestAnimFrame = (function(callback) {
       return window.requestAnimationFrame || window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
              function(callback) {
                 window.setTimeout(callback, 1000 / 120);
              };
    })();

    var width = 800;
    var height = 600;

    function addEdge(nodeA, nodeB) {
      if (nodeA.edges.indexOf(nodeB) == -1) {
        nodeA.edges[nodeA.edges.length] = nodeB;
        nodeB.edges[nodeB.edges.length] = nodeA;
      }
    }

    function createGraph(count) {
      var graph = new Array();
      for (var i = 0; i < count; i++) {
        var node = new Object();
        node.x = Math.floor((Math.random() * width));  
        node.y = Math.floor((Math.random() * height));
        node.edges = new Array();        
        graph[i] = node;  
        if (i > 0) 
          addEdge(graph[i], graph[i - 1]);        
      }

      for (var i = 0; i < count / 2; i++) {
        var a = Math.floor((Math.random() * count));  
        var b = Math.floor((Math.random() * count));  
        addEdge(graph[a], graph[b]);
      }
      return graph;
    }

    function drawEdges(ctx, node) {
      for (var i = 0; i < node.edges.length; i++) {
        var otherNode = node.edges[i];
        ctx.beginPath();
        ctx.moveTo(node.x, node.y);
        ctx.lineTo(otherNode.x, otherNode.y);
        ctx.stroke();
      }
    }

    function drawNode(ctx, node) {
      ctx.beginPath();
      ctx.arc(node.x, node.y, 30, 0, 2 * Math.PI, false);
      ctx.fillStyle = 'green';
      ctx.fill();
      ctx.lineWidth = 5;
      ctx.strokeStyle = '#003300';
      ctx.stroke();
    }    

    function drawGraph(ctx, graph) {
      ctx.fillStyle = 'white';
      ctx.fillRect(0, 0, width, height);
      for (var i = 0; i < graph.length; i++)         
        drawEdges(ctx, graph[i]);      
      for (var i = 0; i < graph.length; i++)         
        drawNode(ctx, graph[i]);      
    }

    function distanceSqr(dx, dy) { 
      return dx * dx + dy * dy; 
    }

    function force(nodeA, nodeB, distanceFn) {
      var dx = nodeA.x - nodeB.x;
      var dy = nodeA.y - nodeB.y;
      var angle = Math.atan2(dy, dx);
      var ds = distanceFn(distanceSqr(dx, dy));
      return { x: Math.cos(angle) * ds, y: Math.sin(angle) * ds };
    }

    function repelForce(distanceSqr) {
      return 5000.0 / distanceSqr;
    }

    function attractForce(distanceSqr) {
      return -distanceSqr / 20000.0;
    }

    function gravityForce(distanceSqr) {
      return -Math.sqrt(distanceSqr) / 1000.0;
    }


    function calculateForces(graph) {
      var forces = new Array();  
      for (var i = 0; i < graph.length; i++) {
        forces[i] = { x: 0.0, y: 0.0 };

        // repelling between nodes:
        for (var j = 0; j < graph.length; j++) {
          if (i == j)
            continue;
          var f = force(graph[i], graph[j], repelForce);
          forces[i].x += f.x;
          forces[i].y += f.y;
        }

        // attraction between connected nodes:
        for (var j = 0; j < graph[i].edges.length; j++) {
          var f = force(graph[i], graph[i].edges[j], attractForce);
          forces[i].x += f.x;
          forces[i].y += f.y;          
        }          

        // gravity:
        var center = { x: 400, y: 300 };
        var f = force(graph[i], center, gravityForce);
        forces[i].x += f.x;
        forces[i].y += f.y;           
      }  
      return forces;
    }

    function updateNodePositions(graph) {
      var forces = calculateForces(graph);
      for (var i = 0; i < graph.length; i++) {
        graph[i].x += forces[i].x;      
        graph[i].y += forces[i].y;           
      }  
    }    

    function animate(graph) {    
      var ctx = document.getElementById("canvas").getContext("2d");
      for (var i = 0; i < 20; i++)
        updateNodePositions(graph);
      drawGraph(ctx, graph);
      requestAnimFrame(function() { animate(graph); });
    }

    animate(createGraph(8));
  </script>
</body>
</html>

このコードがどのように機能するかは、こちら確認できます。ページを更新して別のグラフを取得します。もちろん、全体的な最小値が見つからず、許容範囲を超える交差エッジが存在する場合もあります。そのため、結果に満足できない場合は、ランダムな揺れを追加できます。

アプローチ 2

この問題は、PCB の設計におけるルーティングの問題に似ています。アプローチ 1 で提供されるシンプルで簡単なソリューションに満足できない場合は、自動ルーティング メソッドを使用してソリューションを改善できます。たとえば、ノードをグリッドに配置し、A* アルゴリズムを使用してそれらを接続する最短パスを見つけることができます。

  1. アプローチ 1 を使用して、次善の初期ソリューションを見つけます (オプション)。
  2. すべてのエッジを削除します。ノードをグリッド上に配置します (座標を切り上げます)。2 つのノードが重ならないように、グリッドには十分な解像度が必要です。
  3. 近似の長さの昇順でエッジを並べ替えます (ユークリッドまたはマンハッタン メトリックを使用します)。
  4. エッジごとに A* アルゴリズムを使用して、ノードを接続するための最短ルートを見つけます。コスト関数として、ソース ノードからの距離を使用するだけでなく、以前にルーティングされたエッジによって既に使用されているグリッド ポイントに足を踏み入れるための十分なペナルティを追加します。
  5. 前のステップで見つかったパス上のグリッド ポイントを「取得済み」としてマークし、次のすべてのエッジがこのパスを踏まない/交差しないパスを優先するようにします。

上記のアルゴリズムは貪欲なヒューリスティックであり、残念ながら最適解を保証するものではありません。これは、結果がエッジをルーティングする順序に依存するためです。別のエッジと交差するランダムなエッジを削除してルートを変更することで、ソリューションをさらに改善できます。

ステップ 1. は、グラフ レイアウトをより規則的にし、平均接続距離を小さくするためのオプションですが、交点の数には影響しません (グリッドに十分な解像度がある場合)。

于 2013-09-19T17:40:57.053 に答える
2

私には単純な閉じたポリゴンの抽出のように見えます。これを試して:

  1. 接続の方向を忘れるすべての冗長接続を削除します (双方向のものは重複しています)

  2. すべての閉じたループを見つける

    開始点は常に 2 つ以上の接続 (または 1 つだけ) を持つコンテナーであるため、開始点に戻るまで (このパスを使用済みとして設定)、またはエンドポイントに到達するまで (1 つの接続のみ、このパスも次のように設定)、未使用の隣接コンテナーをループします。使用済み) または交差点に到達するまで (接続数 > 2、このパスも使用済みとして設定)。

  3. コンテナ間に未使用の線がなくなるまで繰り返します。

この後、グラフを交差しない部分に分解します。

接続が交差しないように、それらを再び結合します。共有接続は内部にあり、非共有接続は外部にあります。開ループ (エンドポイントを含む) はどこでもかまいません。

これが役立つことを願っています

于 2013-09-16T17:07:13.260 に答える