ropeladder
ropeladder

Reputation: 1153

d3 v4 update/merge grouped data

I am trying to update data for a force simulation in D3 v4, similar to this unsuccessful attempt and semi-similar to this successful one. CodePen here

Instead of joining the new nodes it appears to be doubling all the nodes (see pictures). It does not seem to be refreshing the graph correctly either.

initial graph (initial graph)

graph after adding data (after adding data)

HTML:

<button onclick="addData()">Add Data</button>
<svg width="960" height="600"></svg>

JavaScript:

function addData() {
    graph.nodes.push({"id": "Extra1", "group": 11},{"id": "Extra2", "group": 11})
    graph.links.push({"source": "Extra1", "target": "Valjean", "strength": 1},{"source": "Extra1", "target": "Extra2", "strength": 2})
    update()
    simulation.restart()
}

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

var link, linkEnter, nodeWrapper, nodeWrapperEnter;

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function(d) { return d.id; }))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2))
    .on("tick", ticked);

var allLinkG = svg.append("g")
    .attr("class", "allLinkG")

var allNodeG = svg.append("g")
    .attr("class", "allNodeG")

update()
simulation.restart()

function update(){

  link = allLinkG
    .selectAll("line")
    .data(graph.links, function(d){ return d.id }) 

  link.exit().remove()

  linkEnter = link
    .enter().append("line");

  link = linkEnter.merge(link).attr("class","merged");


  nodeWrapper = allNodeG
    .selectAll("nodeWrapper")
    .data(graph.nodes, function(d) { return d.id; })

  nodeWrapper.exit().remove();

  nodeWrapperEnter = nodeWrapper.enter()
    .append("g").attr("class","nodeWrapper")
    .append("circle")
    .attr("r", 2.5)

  nodeWrapper = nodeWrapperEnter
    .merge(nodeWrapper).attr("class","merged");


  simulation
      .nodes(graph.nodes);

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

function ticked() {
    link
        .attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });

    nodeWrapper
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });
}

Thanks very much for any help.

Upvotes: 1

Views: 2027

Answers (1)

Mark
Mark

Reputation: 108512

A few issues here:

  1. Your selectAll is selecting by element type nodeWrapper, you meant by class .nodeWrapper.
  2. You change the class name to "merged" after your .merge, you can't do this as it breaks future selections by .nodeWrapper class.
  3. When you .merge your selection, you are merging circles with gs. You should stay consistent and operate on the gs only.

Quick refactor:

function update() {

  link = allLinkG
    .selectAll("line")
    .data(graph.links, function(d) {
      return d.id
    })

  link.exit().remove()

  linkEnter = link
    .enter().append("line");

  link = linkEnter.merge(link).attr("class", "merged");

  nodeWrapper = allNodeG
    .selectAll(".nodeWrapper") //<-- class nodeWrapper
    .data(graph.nodes, function(d) {
      return d.id;
    })

  nodeWrapperEnter = nodeWrapper.enter()
    .append("g").attr("class", "nodeWrapper"); //<-- enter selection should be gs

  nodeWrapperEnter //<-- append  your circles
    .append("circle")
    .attr("r", 2.5)

  nodeWrapper = nodeWrapperEnter //<-- merge, don't change class
    .merge(nodeWrapper);

  nodeWrapper.exit().remove();  //<-- and the exit

  simulation
    .nodes(graph.nodes);

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

Note, I also modified your tick function to operate on the g instead of the circle:

nodeWrapper
  .attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  });

Full code is here.

Upvotes: 2

Related Questions