I am relatively new to coding and very new to d3. I am currently trying to use d3 with json to make a pack layout representing current presidential candidates and how many times they have talked about a certain issue during the feedback.
I wanted to start small so I made some dummy data in a .json file, it is below:
{
"name": "John Doe",
"party": "democratic",
"issues": [
{ "issue":"issue1", "value": 25 },
{ "issue":"issue2", "value": 10 },
{ "issue":"issue3", "value": 50 },
{ "issue":"issue4", "value": 40 },
{ "issue":"issue5", "value": 5 }
]
}
I want to display bubbles with "issue" as the label and "value" as the circle radius, ending up with five different sized circles on my canvas. Below is my index.html file:
var width = 800, height = 600;
var canvas = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(50, 50)");
var pack = d3.layout.pack()
.size([width, height - 50])
.padding(10);
d3.json("fakedata.json", function (data) {
var nodes = pack.nodes(data);
var node = canvas.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("circle")
.attr("r", function (d) { return d.r; })
.attr("fill", "steelblue")
.attr("opacity", 0.25)
.attr("stroke", "#ADADAD")
.attr("stroke-width", "2");
node.append("text")
.text(function (d) {
return d.children ? "" : d.issue;
});
});
I keep getting the error below and I think it is because node is not being set correctly.
Error: Invalid value for <g> attribute transform="translate(NaN,NaN)"
Error: Invalid value for <circle> attribute r="NaN"
Any help would be much appreciated! Thank you!
The JSON you are passing is
{
"name": "John Doe",
"party": "democratic",
"issues": [
{ "issue":"issue1", "value": 25 },
{ "issue":"issue2", "value": 10 },
{ "issue":"issue3", "value": 50 },
{ "issue":"issue4", "value": 40 },
{ "issue":"issue5", "value": 5 }
]
}
It should have been like below note the key name children instead of issues
{
"name": "John Doe",
"party": "democratic",
"children": [
{ "issue":"issue1", "value": 25 },
{ "issue":"issue2", "value": 10 },
{ "issue":"issue3", "value": 50 },
{ "issue":"issue4", "value": 40 },
{ "issue":"issue5", "value": 5 }
]
}
Working code here.
The answer is a bit late, but in case you don't want to change your data structure and you want to keep the issues property, you can explicitly tell D3 to use the issues property for children data using the children() call:
var pack = d3.layout.pack()
.size([width, height - 50])
.children(function (d) { return d.issues; })
.padding(10);
Related
How can I capture the click event for the last node?
I followed this tutorial(http://bl.ocks.org/ganeshv/6a8e9ada3ab7f2d88022) to make the tree map. In my purpose I want to make the last node clickable, then get the node data send it to server.
I used the developer tool to get the structure of tree map.
My code in the js file is that:
function display(d) {
lastGroupArray = [];
// collectLastGroup(d);
console.log(lastGroupArray);
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function (d) { return d._children; })
.classed("children", true)
.on("click", transition);
var children = g.selectAll(".child")
.data(function(d) { return d._children || [d]; })
.enter().append("g");
children.append("rect")
.attr("class", "child")
.call(rect)
.append("title")
.text(function(d) { return d.name + " (" + formatNumber(d.value) + ")"; });
//append child text
children.append("text")
.attr("class", "ctext")
.text(function(d) { return d.name; })
.call(text2);
//append parent text
g.append("rect")
.attr("class", "parent")
.call(rect);
var ccc = g.selectAll("rect").on("click", function (d){
if(typeof d.lastGroup !== 'undefined'){
d.lastGroup.forEach( function (dd) {
// lastGroupArray.push(new LastGroup(dd));
console.log(dd.filePath);
});
}
})
My function above only can capture the last node when it has a parent.
The function will not work if the node is the last child when the treemap is loaded.
The Payee is the last node when the treemap is loaded.
Here is my JSON data:
The payee is on the bottom, it only has one parent which is the root "digit-first2".
{
"children": [
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result1.csv",
"name": "0~2",
"value": 112
}],
"children": [
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result3.csv",
"name": "0~2",
"value": 218
}],
"children": [{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result7.csv",
"name": "0~2",
"value": 836
}],
"name": "Payee",
"value": 836
}],
"name": "Tran Type",
"value": 218
},
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result5.csv",
"name": "0~2",
"value": 834
}],
"name": "Payee",
"value": 834
}
],
"name": "[Code-Finger-Print, [Memo]]",
"value": 112
},
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result2.csv",
"name": "0~2",
"value": 138
}],
"children": [{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result6.csv",
"name": "0~2",
"value": 766
}],
"name": "Payee",
"value": 766
}],
"name": "Tran Type",
"value": 138
},
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result4.csv",
"name": "0~2",
"value": 731
}],
"name": "Payee",
"value": 731
}
],
"name": "digit-first2"
}
My function above will only work when the root is not the parent of the last node.
My question is how can I capture the click event for the node if the root is this node`s parent.
You can achieve the last node click by changing the json structure. The last child should have one more children key with all the last child's properties copied in the children key with the same name .
For example if the last child is AgglomerativeCluster . Then the structure can be
children: [
{
name: "cluster",
children: [
{ name: "AgglomerativeCluster",
children: [{name: "AgglomerativeCluster",value: 3938,count:39}] },
{ name: "CommunityStructure", value: 3812 },
]
}
Find the full implementation here https://codesandbox.io/s/affectionate-thunder-l024x
There is simple example;
function display(d) {
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function (d) { return d._children; })
.classed("children", true)
.on("click", transition);
var children = g.selectAll(".child")
.data(function (d) { return d._children || [d]; })
.enter().append("g");
children.append("rect")
.attr("class", "child")
.call(rect)
.append("title")
.text(function (d) {
return d.key + " (" + formatNumber(d.value) + ")";
});
children.append("text")
.attr("class", "ctext")
.text(function (d) { return d.key; })
.call(text2);
g.append("rect")
.attr("class", "parent")
.call(rect);
var t = g.append("text")
.attr("class", "ptext")
.attr("dy", ".75em")
t.append("tspan")
.text(function (d) { return d.key; });
t.append("tspan")
.attr("dy", "1.0em")
.text(function (d) { return formatNumber(d.value); });
t.call(text);
var isDeph = false;
g.selectAll("rect")
.style("fill", function (d) {
if (d.values != null) {
isDeph = true;
}
return d.color;
})
.attr('onclick', function (d) {
if (!isDeph) {
return d.data
}
return ''
});
function transition(d) {
if (transitioning || !d)
return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function (a, b) {
return a.depth - b.depth;
});
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
t1.selectAll(".ptext").call(text).style("fill-opacity", 0);
t1.selectAll(".ctext").call(text2).style("fill-opacity", 0);
t2.selectAll(".ptext").call(text).style("fill-opacity", 1);
t2.selectAll(".ctext").call(text2).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function () {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
I am working with this code to get a radial tree diagram for my data. However, I'd like to modify it to avoid curved links. Instead I am interested in linear straight connections. The curved links make the illustration to be less sophisticated specially when we have lower number of children nodes. For instance, you may look at the parent node and its links with the nodes on the first layer (circle). How can I use straight lines for these connections?
This is the part of the code I would like to modify to satisfy my needs:
var link = g.selectAll(".link")
.data(root.links())
.enter().append("path")
.attr("class", "link")
.attr("d", d3.linkRadial()
.angle(function(d) { return d.x; })
.radius(function(d) { return d.y; }));
where function is currently defined as
function radialPoint(x, y) {
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
}
Thanks.
To get linear straight connections, don't use a path generator - d3.linkRadial (or d3.linkHorizontal etc) - use a line:
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("line")
.attr("class", "link")
.attr("stroke","#ccc")
.attr("x1", function(d) { return radialPoint(d.source.x,d.source.y)[0]; })
.attr("y1", function(d) { return radialPoint(d.source.x,d.source.y)[1]; })
.attr("x2", function(d) { return radialPoint(d.target.x,d.target.y)[0]; })
.attr("y2", function(d) { return radialPoint(d.target.x,d.target.y)[1]; }) ;
This will keep your links straight, the snippet below should demonstrate this.
var data = { "name": "Root", "children": [
{ "name": "A", "children": [ {"name": "A-1" }, {"name": "A-2" }, {"name":"A-3"}, {"name":"A-4"}, { "name":"A-5"} ] },
{ "name": "B", "children": [ {"name": "B-1" } ] },
{ "name": "C" },
{ "name": "D", "children": [ {"name": "D-1" }, {"name": "D-2" }, {"name": "D-3", "children": [ {"name": "D-3-i"}, {"name":"D-3-ii"} ] } ] },
{ "name": "E" },
{ "name": "F" }
] };
var width = 960;
var height = 500;
margin = {left: 100, top: 100, right: 50, bottom: 50}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g").attr('transform','translate('+ width/2 +','+ height/2 +')');
var root = d3.hierarchy(data);
var tree = d3.tree()
.size([2 * Math.PI, height/2]);
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("line")
.attr("class", "link")
.attr("stroke","#ccc")
.attr("x1", function(d) { return radialPoint(d.source.x,d.source.y)[0]; })
.attr("y1", function(d) { return radialPoint(d.source.x,d.source.y)[1]; })
.attr("x2", function(d) { return radialPoint(d.target.x,d.target.y)[0]; })
.attr("y2", function(d) { return radialPoint(d.target.x,d.target.y)[1]; })
;
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + radialPoint(d.x, d.y) + ")"; })
node.append("circle")
.attr("r", 2.5);
node.append("text")
.text(function(d) { return d.data.name; })
.attr('y',-10)
.attr('x',-10)
.attr('text-anchor','middle');
function radialPoint(x, y) {
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
I'm new to d3 and when I learnt how to draw a force chart, I had some problems about it. And at first, let we see my code here:
<html>
<head>
<meta charset="UTF-8">
<title>the force chart</title>
</head>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var nodes = [ { "id": "English" },{ "id": "Italy" },
{ "id": "America" },{ "id": "Canada" },
{ "id": "Australia" },{ "id": "Japan" },
{ "id": "China" } ];
var edges = [ { "source": 0 , "target": 1 } , { "source": 0 , "target": 2 },
{ "source": 0 , "target": 3 } , { "source": 1 , "target": 4 },
{ "source": 1 , "target": 5 } , { "source": 1 , "target": 6 }, ];
var force = d3.forceSimulation(nodes)
.force("link",d3.forceLink()
.id(function(d){return d.id})
.distance(function(d){return 150}).strength([-400]))
.force("charge",d3.forceManyBody())
.force("center",d3.forceCenter(width , height));
force.restart(); //start
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
//add nodes
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("r",20)
.style("fill",function(d,i){
return color(i);
})
.call(d3.drag()); //to drag the nodes
//add information
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d){
return d.id;
});
force.on("tick", function(){ //update the position of lines
svg_edges.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; })
//update the position of nodes
svg_nodes.attr("cx",function(d){ return d.x; })
.attr("cy",function(d){ return d.y; });
//update the position of information
svg_texts.attr("x",function(d){ return d.x; })
.attr("y",function(d){ return d.y; });
});
</script>
</body>
</html>
I want to draw a picture like this:
But my code can only show one node, just like this:
So I feel confused, because there is no error in Developer Tools. As I layout the force, I infer to https://github.com/d3/d3-force/blob/master/README.md#links . So I solve the problem which is result from the different versions. But why it still doesn't work? Could you help me? I'm very appreciate it if you help me! Thank you!
Besides the points already explained by #Vinod:
.append("circle")
and
.force("center", d3.forceCenter(width/2, height/2));
You have a trailing comma. But the most important is this, to show the edges:
First, add the edges to the simulation:
force.force("link")
.links(edges);
And then, change the links id. Right now, there is no property called id. So, it should be:
.force("link", d3.forceLink()
.id(function(d,i) {
return i
})
//the rest of the function
Here is a demo:
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
"id": "English"
}, {
"id": "Italy"
}, {
"id": "America"
}, {
"id": "Canada"
}, {
"id": "Australia"
}, {
"id": "Japan"
}, {
"id": "China"
}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 1,
"target": 5
}, {
"source": 1,
"target": 6
}];
var force = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d,i) {
return i
})
.distance(function(d) {
return 150
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width/2, height/2));
force.restart(); //start
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
//add nodes
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 20)
.style("fill", function(d, i) {
return color(i);
})
.call(d3.drag()); //to drag the nodes
//add information
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d) {
return d.id;
});
force.nodes(nodes);
force.force("link")
.links(edges);
force.on("tick", function() { //update the position of lines
svg_edges.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;
})
//update the position of nodes
svg_nodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
//update the position of information
svg_texts.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
There are several mistakes in your code
firstly you need to append circle like this
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r",20)
.style("fill",function(d,i){
return color(i);
})
.call(d3.drag()); //to drag the nodes
where as your code append r which is no SVG tag moreover the center of the graph should not be width and height it should be width/2 and height/2 to make it at center
See this fiddle http://jsfiddle.net/Qh9X5/9515/
similarly for lines you are using edges data which don't have the x,y values you need to pass the xy value for drawing lines
For a complete solution note in v3.3 See this https://jsfiddle.net/3uehrfj8/1/ with all nodes and edges
I'm working with d3.js to generate a visualization that represents different hypotheses. Since the hypotheses are made of different parts , each word / part gets its own text element.
I want to base the x-position of each text element on the text width of the previous word including an offset. Having a hypothesis "IF x THEN y" i would need 4 text elements with "IF" having x=0, and since "IF" has a width of 10 and i use an offset of 5 "x" will get x=15 and so on.
I'm using json data that could look like this:
{[
{"id" : "id0",
"elements" : [
{
"text" : "IF",
"type" : "conditional"
},
{
"text" : "X",
"type" : "variable"
},
{
"text" : "THEN",
"type" : "conditional"},
{
"text" : "Y",
"type" : "variable"
}
]},
{"id" : "id1",
"elements" : [
{
"text" : "IF",
"type" : "conditional"
},
{
"text" : "abc",
"type" : "variable"
},
{
"text" : "THEN",
"type" : "conditional"},
{
"text" : "xyz",
"type" : "variable"
}
]}
]}
The code i am using to generate the text elements so far (each hypothesis is in a g-element is
var svg = d3.select("#viewport")
.append("svg")
.attr("width", 1200)
.attr("height", 800);
var content = svg.append("g").attr("id", "drawing");
var groups = content.selectAll().data(arr)
.enter()
.append("g")
.attr("class", function (d) {
return "hypothesis " + d["id"];
})
.each(function (d, i) {
d3.select(this).selectAll("text")
.data(d["elements"])
.enter()
.append("text")
.attr("class", function (d) {
return d.type;
})
.text(function (d) {
return d.text;
})
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("x", function (d, j) {
return j++ * 100;
})
.attr("y", 50 * (i + 1));
});
When setting the x position i want to get the width of the current text element and push it onto a variable so i can get the next new x-coordinate instead of just using a currently random offset of 100 px per word.
So the question is how can i get the calculated text width (have seen things on getBBox or similar, but it didn't work for me since i don't know where to use them) and how to apply it to the text elements. Or if there is a better way to create the elements, maybe not in a single run.
The different elements need to be styled in different colors and have to react so mouse-over later, that's why they have to be single text elements.
Thanks in advance.
I always use getComputedTextLength for these sorts of things, although getBBox would also work:
.each(function(d, i) {
var runningWidth = 0; //<-- keep a running total
...
.attr("x", function(d, j) {
var w = this.getComputedTextLength(), //<-- length of this node
x = runningWidth; //<-- previous length to return
runningWidth += w; //<-- total
return x;
})
...
Full code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div id="viewport"></div>
<script>
var arr =
[{
"id": "id0",
"elements": [{
"text": "IF",
"type": "conditional"
}, {
"text": "X",
"type": "variable"
}, {
"text": "THEN",
"type": "conditional"
}, {
"text": "Y",
"type": "variable"
}]
}, {
"id": "id1",
"elements": [{
"text": "IF",
"type": "conditional"
}, {
"text": "abc",
"type": "variable"
}, {
"text": "THEN",
"type": "conditional"
}, {
"text": "xyz",
"type": "variable"
}]
}];
var svg = d3.select("#viewport")
.append("svg")
.attr("width", 1200)
.attr("height", 800);
var content = svg.append("g").attr("id", "drawing");
var groups = content.selectAll().data(arr)
.enter()
.append("g")
.attr("class", function(d) {
return "hypothesis " + d["id"];
})
.each(function(d, i) {
var runningWidth = 0;
d3.select(this).selectAll("text")
.data(d["elements"])
.enter()
.append("text")
.attr("class", function(d) {
return d.type;
})
.text(function(d) {
return d.text;
})
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("x", function(d, j) {
var w = this.getComputedTextLength(),
x = runningWidth;
runningWidth += w;
return x;
})
.attr("y", 50 * (i + 1));
});
</script>
</body>
</html>
When I add new nodes to D3's Force Layout, the new nodes ignore the previous nodes when positioning itself and the previous nodes becomes un-draggable. I feel I've followed the logic of:
Add elements to arrays nodes and links
Updated force.nodes(nodes) and force.links(links)
Ran through .data().enter() with new data
Called force.start()
But still results in previous nodes disconnects. The new nodes are draggable and appears to take into consideration the LAST SET of added nodes position and avoids collision, all other previous nodes are clickable still, but their positioning are ignored and not updated.
Here is a the code in PLNKR: http://plnkr.co/edit/5fXZf63s73cTO37zLjNQ?p=preview
var width = 1000;
var height = 600;
var node_w = 30;
var node_h = 30;
var text_dx = -20;
var text_dy = 20;
var new_id = 9;
var nodes = [],
links = [],
links_line,
node_circles;
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var nodes = [
{ "name": "Nucleus" , "size" : 25, "id" : 0 , "color":"#ac0000"},
{ "name": "one" , "size" : 5 , "id": 1 , "color": "#ac0"},
{ "name": "two" , "size" : 15 , "id": 2 , "color": "#ac0"},
{ "name": "three" , "size" : 25 , "id": 3 , "color": "#ac0"},
{ "name": "four" , "size" : 9 , "id": 4 , "color": "#ac0"},
{ "name": "five" , "size" : 12 , "id": 5 , "color": "#ac0"},
{ "name": "six" , "size" : 15 , "id": 6 , "color": "#ac0"},
{ "name": "seven" , "size" : 41 , "id": 7 , "color": "#ac0"},
{ "name": "eight" , "size" : 5 , "id": 8 , "color": "#ac0"}
];
var links = [
{ "source": 0 , "target": 1 , "link_info":"r01" },
{ "source": 1 , "target": 2 , "link_info":"r31" },
{ "source": 1 , "target": 3 , "link_info":"r02" },
{ "source": 1 , "target": 4 , "link_info":"r04" },
{ "source": 0 , "target": 5 , "link_info":"r05" },
{ "source": 0 , "target": 6 , "link_info":"r06" },
{ "source": 0 , "target": 7 , "link_info":"r87" },
{ "source": 0 , "target": 8 , "link_info":"r87" }
];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(150)
.charge(-1400);
var drag = force.drag();
init();
function init() {
force.start();
links_line = svg.selectAll("line")
.data(links)
.enter()
.append("line")
.style("stroke", "#ac0")
.style("stroke-width", 1);
node_circles = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.style("fill", function(d) {return d.color;})
.on("dblclick", function(d, i) {
addNodes(i);
})
.call(drag);
draw();
}
function addNodes(i) {
for (c=0; c < Math.floor(Math.random() * 20) + 4; c++) {
nodes.push({"name": "new " + new_id,"size": (Math.floor(Math.random() * 20) + 10),"id": new_id,"color": "#333"})
links.push({"source": i,"target": new_id,"link_info": "r"+i+new_id});
new_id++;
}
// Update force.nodes
force.nodes(nodes);
// Update force.links
force.links(links);
// exec init()
init();
}
function draw() {
var ticksPerRender = 1;
requestAnimationFrame(function render() {
force.tick();
//Update nodes
node_circles.attr("cx", function(d) {return d.x - d.size / 6;});
node_circles.attr("cy", function(d) {return d.y - d.size / 6;});
node_circles.attr("r", function(d) {return d.size});
//Update Location line
links_line.attr("x1", function(d) {return d.source.x;});
links_line.attr("y1", function(d) {return d.source.y;});
links_line.attr("x2", function(d) {return d.target.x;});
links_line.attr("y2", function(d) {return d.target.y;});
requestAnimationFrame(render)
});
} // draw();
Updating a d3 visualization follows an enter, update, and exit workflow (start your reading here and here).
Try this instead:
function init() {
force.start();
links_line = svg.selectAll("line")
.data(links);
links_line
.enter()
.append("line")
.style("stroke", "#ac0")
.style("stroke-width", 1);
links_line.exit().remove();
node_circles = svg.selectAll("circle")
.data(nodes);
node_circles
.enter()
.append("circle")
.style("fill", function(d) {return d.color;})
.on("dblclick", function(d, i) {
addNodes(i);
})
.call(drag);
draw();
}
Updated example.