I'm using a force layout graph to show a network but I have problems when updating my data.
I already check How to update elements of D3 force layout when the underlying data changes, and of course the "Modifying a Force Layout" as well as the "General Update Pattern" by "mbostock" from D3.js (unfortunately, I can only post a maximum of two links...).
My code based on the "Mobile Patent Suits" example with some modifications and differences.
You can check my full code here:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
</style>
<body>
<!-- add an update button -->
<div id="update">
<input name="updateButton" type="button" value="Update" onclick="newData()"/>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500;
var force = d3.layout.force()
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black");
var dataset = [
{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "HTC", type: "licensing"},
{source: "Samsung", target: "Apple", type: "suit"},
{source: "Motorola", target: "Apple", type: "suit"},
{source: "Nokia", target: "Apple", type: "resolved"},
{source: "HTC", target: "Apple", type: "suit"},
{source: "Kodak", target: "Apple", type: "suit"},
{source: "Microsoft", target: "Barnes & Noble", type: "suit"},
{source: "Microsoft", target: "Foxconn", type: "suit"},
{source: "Oracle", target: "Google", type: "suit"},
{source: "Apple", target: "HTC", type: "suit"},
{source: "Microsoft", target: "Inventec", type: "suit"},
{source: "Samsung", target: "Kodak", type: "resolved"},
{source: "LG", target: "Kodak", type: "resolved"},
{source: "RIM", target: "Kodak", type: "suit"},
{source: "Sony", target: "LG", type: "suit"},
{source: "Kodak", target: "LG", type: "resolved"},
{source: "Apple", target: "Nokia", type: "resolved"},
{source: "Qualcomm", target: "Nokia", type: "resolved"},
{source: "Apple", target: "Motorola", type: "suit"},
{source: "Microsoft", target: "Motorola", type: "suit"},
{source: "Motorola", target: "Microsoft", type: "suit"},
{source: "Huawei", target: "ZTE", type: "suit"},
{source: "Ericsson", target: "ZTE", type: "suit"},
{source: "Kodak", target: "Samsung", type: "resolved"},
{source: "Apple", target: "Samsung", type: "suit"},
{source: "Kodak", target: "RIM", type: "suit"},
{source: "Nokia", target: "Qualcomm", type: "suit"}
];
var path = svg.append("g").selectAll("path"),
circle = svg.append("g").selectAll("circle"),
text = svg.append("g").selectAll("text"),
marker = svg.append("defs").selectAll("marker");
var nodes = {};
update(dataset);
function newData()
{
var newDataset = [
{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "HTC", type: "licensing"},
{source: "Samsung", target: "Apple", type: "suit"},
];
update(newDataset);
}
function update(links)
{
// Compute the distinct nodes from the links.
links.forEach(function(link)
{
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
force
.nodes(d3.values(nodes))
.links(links)
.start();
// -------------------------------
// Compute the data join. This returns the update selection.
marker = marker.data(["suit", "licensing", "resolved"]);
// Remove any outgoing/old markers.
marker.exit().remove();
// Compute new attributes for entering and updating markers.
marker.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("line") // use ".append("path") for 'arrows'
.attr("d", "M0,-5L10,0L0,5");
// -------------------------------
// Compute the data join. This returns the update selection.
path = path.data(force.links());
// Remove any outgoing/old paths.
path.exit().remove();
// Compute new attributes for entering and updating paths.
path.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
// -------------------------------
// Compute the data join. This returns the update selection.
circle = circle.data(force.nodes());
// Add any incoming circles.
circle.enter().append("circle");
// Remove any outgoing/old circles.
circle.exit().remove();
// Compute new attributes for entering and updating circles.
circle
.attr("r", 6)
.call(force.drag);
// -------------------------------
// Compute the data join. This returns the update selection.
text = text.data(force.nodes());
// Add any incoming texts.
text.enter().append("text");
// Remove any outgoing/old texts.
text.exit().remove();
// Compute new attributes for entering and updating texts.
text
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick()
{
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d)
{
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d)
{
return "translate(" + d.x + "," + d.y + ")";
}
</script>
A JSFiddle of my code can be found here: http://jsfiddle.net/5m8a9/
After pressing the "Update" button I want to update my graph dynamically. So far so good, the problem is, that ONLY the paths will be updated but not the circles or the texts (some circles and the corresponding texts still remain and will not be removed) as you can see at my JSFiddle link.
I tried to figure out the problem for the last couple of days without success.
What am I missing and how can I make my code work as aspected?
If anyone can help I would be eternally grateful.
Edited to add final solution as @AmeliaBR provided:
Here is the hole code to my final solution:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
</style>
<body>
<!-- add an update button -->
<div id="update">
<input name="updateButton" type="button" value="Update" onclick="newData()"/>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500;
var force = d3.layout.force()
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black");
var dataset = [
{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "HTC", type: "licensing"},
{source: "Samsung", target: "Apple", type: "suit"},
{source: "Motorola", target: "Apple", type: "suit"},
{source: "Nokia", target: "Apple", type: "resolved"},
{source: "HTC", target: "Apple", type: "suit"},
{source: "Kodak", target: "Apple", type: "suit"},
{source: "Microsoft", target: "Barnes & Noble", type: "suit"},
{source: "Microsoft", target: "Foxconn", type: "suit"},
{source: "Oracle", target: "Google", type: "suit"},
{source: "Apple", target: "HTC", type: "suit"},
{source: "Microsoft", target: "Inventec", type: "suit"},
{source: "Samsung", target: "Kodak", type: "resolved"},
{source: "LG", target: "Kodak", type: "resolved"},
{source: "RIM", target: "Kodak", type: "suit"},
{source: "Sony", target: "LG", type: "suit"},
{source: "Kodak", target: "LG", type: "resolved"},
{source: "Apple", target: "Nokia", type: "resolved"},
{source: "Qualcomm", target: "Nokia", type: "resolved"},
{source: "Apple", target: "Motorola", type: "suit"},
{source: "Microsoft", target: "Motorola", type: "suit"},
{source: "Motorola", target: "Microsoft", type: "suit"},
{source: "Huawei", target: "ZTE", type: "suit"},
{source: "Ericsson", target: "ZTE", type: "suit"},
{source: "Kodak", target: "Samsung", type: "resolved"},
{source: "Apple", target: "Samsung", type: "suit"},
{source: "Kodak", target: "RIM", type: "suit"},
{source: "Nokia", target: "Qualcomm", type: "suit"}
];
var path = svg.append("g").selectAll("path"),
circle = svg.append("g").selectAll("circle"),