2016-08-28 11 views
5

ノード(リンクなし)を動的に追加および削除できる単純なフォースレイアウトを実装しようとしています。私はD3バージョン3でコンセプトを実装するのに成功しましたが、バージョン4に変換することができません。ノードを追加および更新した後、シミュレーションがフリーズし、受信サークルがsvgの左上隅に描画されます。誰かがなぜこれが当てはまるのか知っていますか?おかげで任意のヘルプ:)バージョン4のD3 Force Layoutにノードを動的に追加

のための私のコンセプトは、このソリューションに基づいています。 Adding new nodes to Force-directed layout

JSFiddle: working code in d3 v3

/* Define class */ 
class Planet { 
    constructor(selector) { 
    this.w = $(selector).innerWidth(); 
    this.h = $(selector).innerHeight(); 

    this.svg = d3.select(selector) 
     .append('svg') 
     .attr('width', this.w) 
     .attr('height', this.h); 

    this.force = d3.layout.force() 
     .gravity(0.05) 
     .charge(-100) 
     .size([this.w, this.h]); 

    this.nodes = this.force.nodes(); 
    } 

    /* Methods (are called on object) */ 
    update() { 
    /* Join selection to data array -> results in three new selections enter, update and exit */ 
    const circles = this.svg.selectAll('circle') 
     .data(this.nodes, d => d.id); // arrow function, function(d) { return d.y;} 

    /* Add missing elements by calling append on enter selection */ 
    circles.enter() 
     .append('circle') 
     .attr('r', 10) 
     .style('fill', 'steelblue') 
     .call(this.force.drag); 

    /* Remove surplus elements from exit selection */ 
    circles.exit() 
     .remove(); 

    this.force.on('tick',() => { 
     circles.attr('cx', d => d.x) 
     .attr('cy', d => d.y); 
    }); 

    /* Restart the force layout */ 
    this.force.start(); 
    } 

    addThought(content) { 
    this.nodes.push({ id: content }); 
    this.update(); 
    } 

    findThoughtIndex(content) { 
    return this.nodes.findIndex(node => node.id === content); 
    } 

    removeThought(content) { 
    const index = this.findThoughtIndex(content); 
    if (index !== -1) { 
     this.nodes.splice(index, 1); 
     this.update(); 
    } 
    } 
} 

/* Instantiate class planet with selector and initial data*/ 
const planet = new Planet('.planet'); 
planet.addThought('Hallo'); 
planet.addThought('Ballo'); 
planet.addThought('Yallo'); 

これはV4にコードを変換する私の意図です:

/* Define class */ 
class Planet { 
    constructor(selector) { 
    this.w = $(selector).innerWidth(); 
    this.h = $(selector).innerHeight(); 

    this.svg = d3.select(selector) 
     .append('svg') 
     .attr('width', this.w) 
     .attr('height', this.h); 

    this.simulation = d3.forceSimulation() 
     .force('charge', d3.forceManyBody()) 
     .force('center', d3.forceCenter(this.w/2, this.h/2)); 

    this.nodes = this.simulation.nodes(); 
    } 

    /* Methods (are called on object) */ 
    update() { 
    /* Join selection to data array -> results in three new selections enter, update and exit */ 
    let circles = this.svg.selectAll('circle') 
     .data(this.nodes, d => d.id); // arrow function, function(d) { return d.y;} 

    /* Add missing elements by calling append on enter selection */ 
    const circlesEnter = circles.enter() 
     .append('circle') 
     .attr('r', 10) 
     .style('fill', 'steelblue'); 

    circles = circlesEnter.merge(circles); 

    /* Remove surplus elements from exit selection */ 
    circles.exit() 
     .remove(); 

    this.simulation.on('tick',() => { 
     circles.attr('cx', d => d.x) 
     .attr('cy', d => d.y); 
    }); 

    /* Assign nodes to simulation */ 
    this.simulation.nodes(this.nodes); 

    /* Restart the force layout */ 
    this.simulation.restart(); 
    } 

    addThought(content) { 
    this.nodes.push({ id: content }); 
    this.update(); 
    } 

    findThoughtIndex(content) { 
    return this.nodes.findIndex(node => node.id === content); 
    } 

    removeThought(content) { 
    const index = this.findThoughtIndex(content); 
    if (index !== -1) { 
     this.nodes.splice(index, 1); 
     this.update(); 
    } 
    } 
} 
+0

私は現在、同様の問題で立ち往生したが、キャンバスとしています。私はまだ解決策はありませんが、あなたの問題はあなたが共有した投稿で強調表示されていると思います:** "第2の重要な部分は、ノードとリンク配列がforce()に基づいていなければならないということです。ノードが削除されて追加されると、モデルは同期しなくなります。 "**ノードを追加しても、座標を定義していません。 –

答えて

3

Please see plunkr example

私はキャンバスを使用していますが、理論は同じです:

あなたは元の配列に追加する前に、まずD3のコア機能にノードとリンクのご新しい配列を与えなければなりません。

drawData: function(graph){ 
    var countExtent = d3.extent(graph.nodes,function(d){return d.connections}), 
     radiusScale = d3.scalePow().exponent(2).domain(countExtent).range(this.nodes.sizeRange); 

     // Let D3 figure out the forces 
     for(var i=0,ii=graph.nodes.length;i<ii;i++) { 
     var node = graph.nodes[i]; 

     node.r = radiusScale(node.connections); 
     node.force = this.forceScale(node); 
     }; 

    // Concat new and old data 
    this.graph.nodes = this.graph.nodes.concat(graph.nodes); 
    this.graph.links = this.graph.links.concat(graph.links); 

    // Feed to simulation 
    this.simulation 
     .nodes(this.graph.nodes); 

    this.simulation.force("link") 
     .links(this.graph.links); 

    this.simulation.alpha(0.3).restart(); 
} 

その後、新しいデータで再起動するようにD3に指示します。

D3があなたのtick()関数を呼び出すとき、あなたのSVG要素にどの座標を適用する必要があるかは既に分かっています。

ticked: function(){ 
    if(!this.graph) { 
     return false; 
    } 

    this.context.clearRect(0,0,this.width,this.height); 
    this.context.save(); 
    this.context.translate(this.width/2, this.height/2); 

    this.context.beginPath(); 
    this.graph.links.forEach((d)=>{ 
     this.context.moveTo(d.source.x, d.source.y); 
     this.context.lineTo(d.target.x, d.target.y); 
    }); 
    this.context.strokeStyle = this.lines.stroke.color; 
    this.context.lineWidth = this.lines.stroke.thickness; 

    this.context.stroke(); 

    this.graph.nodes.forEach((d)=>{ 
     this.context.beginPath(); 

     this.context.moveTo(d.x + d.r, d.y); 
     this.context.arc(d.x, d.y, d.r, 0, 2 * Math.PI); 

     this.context.fillStyle = d.colour; 
     this.context.strokeStyle =this.nodes.stroke.color; 
     this.context.lineWidth = this.nodes.stroke.thickness; 
     this.context.fill(); 
     this.context.stroke(); 
    }); 

    this.context.restore(); 
} 

Plunkr example

関連する問題