前提・実現したいこと
グラフの自動描画アルゴリズムをJavaScriptで実装して、SVGとして描画したい。
発生している問題・エラーメッセージ
リポジトリーに置いたHTMLをブラウザーで開いて「再配置する」というボタンをクリックすると、Fruchterman-Reingold アルゴリズムで各ノードの適切な位置を計算してくれるはずなのですが、最終的にすべてのノードが左上に集まってしまいます。
デバッガーでステップ実行すると分かるのですが、iteration毎にノードが描画領域の右下に移動してしまい、最終的にすべてのノードが描画領域における右下の角に配置され、結果次のiterationで位置を表すベクトルの各成分がNaN
になってしまいます(すべてのノードが同じ位置に移動したことになるので)。
該当のソースコード
こちらが実際に Fruchterman-Reingold アルゴリズムを実装した関数です。
実際に動くHTMLはこちらにあります。要件の都合上、HTMLファイル単体で動作するように実装しておりますので、試していただける場合はダウンロードして開いてみてください。
js
1// 実際に Fruchterman-Reingold アルゴリズムを実装している関数。 2// 利用している主な型は次のとおり: 3//// Vector は x と y を数値として持つオブジェクト 4// type Vector = { x: number, y: number } 5// type NodeId = int; 6//// Node は Vector に displacement と id を加えたオブジェクト 7// type Node = { x: number, y: number, id: NodeId, displacement: Vector } 8//// NodeId をキー、 Node の実態を値として持つ連想配列 9// nodes :: Array<NodeId, Node> 10//// NodeId をキー、 NodeId に該当する Node がつながっている NodeId の配列を値とする 11// graph :: Array<NodeId, NodeId[]> 12const placeNodes = () => { 13 const rootArea = rootHeight * rootWidth; 14 const nodeCount = nodes.length; 15 const c = 1; 16 const k = c * Math.sqrt(rootArea / nodes.length); 17 18 const attraction = (distance) => { 19 return Math.pow(distance, 2) / k; 20 } 21 22 const repulsion = (distance) => { 23 return - Math.pow(k, 2) / distance; 24 } 25 26 const iterations = 50; 27 28 let t = rootWidth / 10; 29 const dt = t / iterations + 1; 30 31 for (let i = 0; i < iterations; i++) { 32 for (const node1 of nodes){ 33 node1.displacement = { x: 0, y: 0 }; 34 for (const node2 of nodes){ 35 if (node1.id !== node2.id) { 36 const dx = node1.x - node2.x; 37 const dy = node1.y - node2.y; 38 const distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); 39 const rep = repulsion(distance); 40 41 node1.displacement.x = node1.displacement.x + (dx / distance) * rep; 42 node1.displacement.y = node1.displacement.y + (dy / distance) * rep; 43 } 44 } 45 for (const node2Id of graph[node1.id]) { 46 if (node1.id !== node2Id) { 47 const node2 = nodes[node2Id]; 48 const dx = node1.x - node2.x; 49 const dy = node1.y - node2.y; 50 const distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); 51 const att = attraction(distance); 52 53 node1.displacement.x = node1.displacement.x - (dx / distance) * att; 54 node1.displacement.y = node1.displacement.y - (dy / distance) * att; 55 56 node2.displacement.x = node2.displacement.x + (dx / distance) * att; 57 node2.displacement.y = node2.displacement.y + (dy / distance) * att; 58 } 59 } 60 } 61 62 for (const node of nodes){ 63 const length = Math.sqrt(Math.pow(node.displacement.x, 2) + Math.pow(node.displacement.y, 2)); 64 node.x = node.x + (node.displacement.x / length) * Math.min(node.displacement.x, t); 65 node.y = node.y + (node.displacement.y / length) * Math.min(node.displacement.y, t); 66 67 // 中心が0ではないため、元の論文の式のとおりではなく、最も端の点と比較する 68 node.x = Math.min(rootWidth, Math.max(0, node.x)); 69 node.y = Math.min(rootHeight, Math.max(0, node.y)); 70 } 71 72 t = t - dt; 73 } 74};
試したこと
定数c
やノードの数、描画領域の大きさなど、いくつかのパラメーターを調整しましたが、結果は変わりませんでした。
補足情報(FW/ツールのバージョンなど)
試したブラウザー
ブラウザー依存の問題ではないとは思いますが念の為:
- Firefox 87.0
- Chromium Edge ver. 89.0.774.75
どちらもWindows 64bit版です。
参考にしたページ
Fruchterman-Reingold アルゴリズムを実装する際、下記の記事とその参照している論文を参考にしました:
後者の元ネタとなった論文をダウンロードすれば、アルゴリズムの詳しい解説や、擬似コードが載っています。
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/04/13 00:29