I am having an annoying issue with my d3 force directed map I'm building where I initially render the page with the nodes and links I need, then periodically check for new information via ajax. When I need a new node and link I draw them, which is fine
However because of the way SVG layers elements, new links draw over older nodes, so where I have nodes as circles and draw lines between them, any new nodes added draw over the top of the circles on older nodes. See image below:
(http://i40.tinypic.com/4fx25j.gif)
I know it is not technically a d3 issue but there must be a way of fixing this. I did try deleting all the circles and redrawing them, but the issue is that the svg:g node it is attached to is too low in the layers so it is still drawn over.
Demo at jsfiddle - look at the following section
draw() {
...
}
as that is where the magic happens. http://jsfiddle.net/zuzzy/uwAhy/
I have simulated the ajax using a 5 second timer, it was easier for the demo.
Any ideas?
As far as I am aware, you can only control the depth of an SVG element by it's position in the DOM.
So what might work for you is to create two groups <g id='lines'>
and <g id='circles'>
.
When you append
your elements, all of the lines should be added to the first group, and all of the circles to the second.
You might have to alter the way that you add the elements, but so long as you make sure that the lines
group appears before the circles
group then you should be golden.
I apologise if this totally does not fit your implementation. I ran into a very similar problem and found the only resolution for me was to draw the 'lower' elements first.
Worked first time! I had already grouped all my elements under one so I just replaced:
var vis = d3.select("body")
.append("svg:svg")
.attr("pointer-events", "all");
.append('svg:g')
where i used vis.xxxx to render both links and circles, with
var vis = d3.select("body")
.append("svg:svg")
.attr("pointer-events", "all");
var linkvis = vis.append('svg:g')
.attr("id","link_elements");
vis = vis.append('svg:g')
.attr("id","node_elements");
and referred to linkvis when drawing the links and vis drawing the circles.
(NB I know this should be a comment but I couldn't fit it in and I thought it might be helpful for someone. @Paul's answer has been marked as the answer)
Another way of resolving this issue would be to use insert
method as shown in following code.
link.enter().insert("line",".node"); //Inserts link element before the first DOM element with class node.
Posting just because this may be helpful for other users who search solution for this question.
//Settings:
//width, height and the default radius of the circles
var w = 1024,
h = 768,
r = 10;
//test data - usually this is recieved via ajax
//Initial Data:
var hosts = eval({
"ITEM003": {
"name": "ITEM003",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"ITEM004": {
"name": "ITEM004",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"CORE": {
"name": "CORE",
"parents": ["ITEM004", "ITEM003"],
"status": 0,
"hostgroup": "DMZ"
}
});
var mylinks = eval({
"0": ["CORE", "ITEM004"],
"1": ["CORE", "ITEM003"]
});
//Data after update
var updated_hosts = eval({
"ITEM003": {
"name": "ITEM003",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"ITEM004": {
"name": "ITEM004",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"CORE": {
"name": "CORE",
"parents": ["ITEM004", "ITEM003"],
"status": 0,
"hostgroup": "DMZ"
},
"REMOTE": {
"name": "REMOTE",
"parents": [],
"status": 0,
"hostgroup": ""
}
});
var updated_mylinks = eval({
"0": ["CORE", "ITEM004"],
"1": ["CORE", "ITEM003"],
"2": ["CORE", "REMOTE"]
});
//I define these here so they carry between functions - not really necessary in this jsfiddle probably
window.link = undefined;
window.node = undefined;
//make up my node object
window.nodeArray = [];
window.node_hash = [];
for (var key in hosts) {
var a = {
id: "node_" + hosts[key].name,
labelText: hosts[key].name,
status: hosts[key].status,
hostgroup: hosts[key].hostgroup,
class: "node realnode",
iconimage: hosts[key].iconimage,
added: true
};
nodeArray.push(a);
node_hash[key] = a;
}
//make up my link object
window.linkArray = [];
for (var key in mylinks) {
var linkcolor = "#47CC60";
var a = {
source: node_hash[mylinks[key][0]],
target: node_hash[mylinks[key][1]],
color: linkcolor,
class: "link reallink"
};
linkArray.push(a);
}
//make up my node text objects
//these are just more nodes with a different class
//we will append text to them later
//we also add the links to the linkArray now to bind them to their real nodes
window.text_hash = [];
for (var key in hosts) {
var a = {
id: "label_" + hosts[key].name,
text: hosts[key].name,
color: "#ffffff",
size: "6",
class: "node label",
added: true
};
nodeArray.push(a);
text_hash[key] = a;
}
//because the text labels are in the same order as the
//original nodes we know that node_hash[0] has label text_hash[0]
//it doesn't matter which we iterate through here
for (var key in text_hash) {
var a = {
source: node_hash[key],
target: text_hash[key],
class: "link label"
};
linkArray.push(a);
}
//set up the environment in a div called graph using the settings baove
window.vis = d3.select("body")
.append("svg:svg")
.attr("height", 500)
.attr("width", 500)
.attr("pointer-events", "all")
.append('svg:g')
//object to interact with the force libraries in d3
//the settings here set how the nodes interact
//seems a bit overcomplicated but it stops the diagram going nuts!
window.force = d3.layout.force()
.friction("0.7")
.gravity(function(d, i) {
if (d.class == "link reallink") {
return "0.95";
} else {
return "0.1";
}
})
.charge(function(d, i) {
if (d.class == "link reallink") {
return "-1500";
} else {
return "-300";
}
})
.linkDistance(function(d) {
if (d.class == "link reallink") {
return "120";
} else {
return "35";
}
})
.linkStrength(function(d) {
if (d.class == "link reallink") {
return "8";
} else {
return "6";
}
})
.nodes(nodeArray)
.links(linkArray)
.on("tick", tick)
node = vis.selectAll(".node");
link = vis.selectAll(".link");
//create the objects and run it
draw();
for (key in nodeArray) {
nodeArray[key].added = false;
}
//wait 5 seconds then update the diagram TO ADD A NODE
setTimeout(function() {
//update the objects
//vis.selectAll("g.node").data(nodeArray).exit().transition().ease("elastic").remove();
//vis.selectAll("line").data(linkArray).exit().transition().ease("elastic").remove();
var a = {
id: "node_REMOTE",
labelText: "REMOTE",
status: "0",
hostgroup: "",
class: "node realnode",
iconimage: "",
added: true
};
nodeArray.push(a);
node_hash["REMOTE"] = a;
var linkcolor = "#47CC60";
var a = {
source: node_hash["CORE"],
target: node_hash["REMOTE"],
color: linkcolor,
class: "link reallink"
};
linkArray.push(a);
//make up my node text objects
var a = {
id: "label_REMOTE",
text: "REMOTE",
color: "#000000",
size: "6",
class: "node label",
added: true
};
nodeArray.push(a);
text_hash["REMOTE"] = a;
var a = {
source: node_hash["REMOTE"],
target: text_hash["REMOTE"],
class: "link label"
};
linkArray.push(a);
//redraw it
draw();
}, 5000);
//----- functions for drawing and tick below
function draw() {
link = link.data(force.links(), function(d) {
return d.source.id + "-" + d.target.id;
});
node = node.data(force.nodes(), function(d) {
return d.id;
});
//create the link object using the links object in the json
//link = vis.selectAll("line").data(linkArray);
link.enter().insert("line", ".node")
.attr("stroke-width", '0')
.transition()
.duration(1000)
.ease("bounce")
.attr("stroke-width", function(d, i) {
if (d.class == 'link reallink') {
return '3';
} else {
return '0';
};
})
.style("stroke", function(d, i) {
return d.color;
})
.attr("class", function(d, i) {
return d.class;
});
//node = vis.selectAll("g").data(nodeArray);
node.enter().append("svg:g")
.attr("class", function(d) {
return d.class
})
.attr("id", function(d) {
return d.id
})
.call(force.drag);
//append to each node an svg circle element
vis.selectAll(".realnode").filter(function(d) {
return d.added;
})
.append("svg:circle")
.attr("r", "0")
.transition()
.duration(1000)
.ease("bounce")
.attr("r", "6")
.style("fill", "#000000")
.style("stroke", function(d) {
return d.color;
})
.style("stroke-width", "4");
//append to each node the attached text desc
vis.selectAll(".label").filter(function(d) {
return d.added;
})
.append("svg:text")
.attr("text-anchor", "middle")
.attr("fill", "black")
.style("pointer-events", "none")
.attr("font-size", "9px")
.attr("font-weight", "100")
.text(function(d) {
return d.text;
})
.attr("transform", "rotate(180)")
.transition()
.duration(1000)
.ease("bounce")
.attr("transform", "rotate(0)");
node.exit().transition().ease("elastic").remove();
link.exit().transition().ease("elastic").remove();
//activate it all - initiate the nodes and links
force.start();
}
function tick() {
node.attr("cx", function(d) {
return d.x = Math.max(r + 15, Math.min(w - r - 15, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(r + 15, Math.min(h - r - 15, d.y));
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
link.data(linkArray).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;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With