nyi
nyi

Reputation: 3229

Adding a node dynamically in d3 canvas

I am new to d3. I have tried few examples with JSON nodes and got a nice working network graph.

However, when I tried to add an extra node dynamically, it does not appear on the screen.

I started with Force Dragging III Canvas and then tried to add the following code, but it does not work; how can I fix this, please help?

graph.addNode("NewNode");

function addNode (id) {
    nodes.push({"id":id});
    restart();

}


function restart() {
  node = node.data(nodes);

  node.enter().insert("circle", ".cursor")
      .attr("class", "node")
      .attr("r", 5);


  node.exit()
      .remove();

  link = link.data(links);

  link.enter().insert("line", ".node")
      .attr("class", "link");
  link.exit()
      .remove();

  force.start();
}

Upvotes: 1

Views: 716

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38151

There are a few thing at play with forces that aren't very obvious:

  • Upon initializing, the nodes in the original data array gain a bunch of new properties
  • Upon initalizing, the links in the original data array are converted to a new form.

Also, if you are following the linked example, there is no need for an enter/update/exit style cycle - the force is using the original data array, not any DOM elements.

Nodes

In your reference example, the nodes start with just an id and a group:

{"source": "Montparnasse", "target": "Babet", "value": 2}

However, after initialized with simulation.nodes(nodes), they get some added parameters:

group: 1
id: "Myriel"
index: 0
vx: 0.0026819653036056477
vy: -0.0005005454729913666
x: 306.003096668046
y: 359.07887474183434

These additional four properties track the node. Each tick requires those properties to be existing already, if they aren't present we get problems. So if we provide a new node, we can give it x,y,vy,vx properties so that it is added seamlessly (example, click to add a node in all examples). This gives an easy addNode function:

  function addNode () {
    var id = graph.nodes.length;
    graph.nodes.push({"id":id, index: id, x: width/2, y: height/2, vx: 0, vy: 0});
  }

Alternatively we can re-initialize the nodes with simulation.nodes() (example), and the force will place it automatically while giving it the required position. However, this is jumpy unless you specify an x and y coordinate (example).

The last one gives us a pretty straight forward addNode function too:

  function addNode () {
    var id = graph.nodes.length;
    graph.nodes.push({"id":id, group: 12, x: width/2, y: height/2});
    simulation.nodes(graph.nodes)
  }

That should help adding nodes to a force.

Links

For links, the original data in your reference example look like this upon loading:

{ source: "Napoleon", target: "Myriel", value: 1 }

However, after initializing the links, the take this form:

index: 0
source: Object { id: "Napoleon", group: 1, index: 1, … } 
target: Object { id: "Myriel", group: 1, index: 0, … }
value: 1

The source and target properties are converted into the objects representing each point. Just as with nodes, the ticked function requires the nodes to be in the latter form. Same as above, we can try to replicate the format, or use the force to set the format itself, I choose the latter for the link, while setting x,y for the new node here. This is the addNode function for this:

  function addNode () {
    var id = graph.nodes.length;
    graph.nodes.push({"id": id, x: width/2, y: height/2});
    graph.links.push({source: "Javert", target: id });

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

As a more complex, but less theoretical, example this question/answer might be interesting (and associated block (click to add/remove nodes, toggle which action with button in top left)), where nodes are added/removed which in turn affects force and voronoi

Upvotes: 4

Related Questions