简体   繁体   中英

d3 / svg layering issue when adding new nodes after a time

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 dx = Math.max(r + 15, Math.min(w - r - 15, dx)); }) .attr("cy", function(d) { return dy = Math.max(r + 15, Math.min(h - r - 15, dy)); }) .attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; }); 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM