admin管理员组文章数量:1353618
I'm seeing a weird behaviour in my D3 application and after hours of trying to figure out what's happening I hope someone can point me at the thing I obviously do wrong.
I have simplified the app down to be very simple and still exhibit the problem. As you'll see it's derived from all the great D3 examples out there. The simple scenario I have an issue with is: select a node (by clicking on it) and, upon hitting the delete key remove said node along with all related links and labels of both the node and the links.
The code pasted below is nearly there since it decreases the number of Nodes and Links exactly as anticipated (given any particular graph) but there is one issue: both the node and link labels are not the correct ones and end up distributed over different circles...
Any idea as to what might be going on would be greatly appreciated!
Code:
var width = 960,
height = 700,
colors = d3.scale.category20();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(.05)
.distance(200)
.charge(-150)
.size([width, height]);
var jsonnodes, jsonlinks;
var node, link, label;
var selected_node = null,
mousedown_node = null,
mousedown_link = null;
d3.json("graph.json", jsondatacallback);
//
// Functions
//
function jsondatacallback(error, json) {
jsonnodes = json.nodes;
jsonlinks = json.links;
force.nodes(jsonnodes)
.links(jsonlinks);
//
// Nodes
//
node = svg.selectAll(".node")
.data(jsonnodes);
node.enter().append("g")
.attr("class", "node")
.on('mousedown', function(d) {
mousedown_node = d;
if (mousedown_node === selected_node)
selected_node = null;
else
selected_node = mousedown_node;
})
.call(force.drag);
node.append("circle")
.attr('r', 11)
.style('stroke', function(d) {
return d3.rgb(colors(d.name)).darker().toString();
});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
//
// Links
//
link = svg.selectAll(".link")
.data(jsonlinks);
link.enter().append("line")
.attr("class", "link");
//
// Labels (for links)
//
label = svg.selectAll(".label")
.data(jsonlinks);
label.enter().append("text")
.attr("class", "label");
label.attr("dx", 12)
.attr("dy", ".35em")
.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
.attr("y", function(d) {return (d.source.y + d.target.y) / 2;})
.text(function(e) {
return Math.random().toString(36).substring(7); ;
});
force.on("tick", function() {
link.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;});
node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
label.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
.attr("y", function(d) {return (d.source.y + d.target.y) / 2;});
});
d3.select(window)
.on("keydown", keydown);
restart();
}
function keydown() {
d3.event.preventDefault();
var lastKeyDown = d3.event.keyCode;
if (!selected_node)
return;
switch (d3.event.keyCode) {
case 8: // backspace
case 46: // delete
if (selected_node) {
removeNode(selected_node);
removeLinks(selected_node);
}
selected_node = null;
restart();
break;
}
}
function restart() {
//
// nodes
//
node = svg.selectAll(".node")
.data(jsonnodes);
node.exit().remove();
node.style('fill', function(d) {
return (d === selected_node) ? d3.rgb(colors(d.name)).brighter().toString() : colors(d.name);
})
.on('mousedown', function(d) {
mousedown_node = d;
if (mousedown_node === selected_node)
selected_node = null;
else
selected_node = mousedown_node;
restart();
});
node.enter().append("g")
.attr("class", "node")
.on('mousedown', function(d) {
mousedown_node = d;
if (mousedown_node === selected_node)
selected_node = null;
else
selected_node = mousedown_node;
});
node.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return Math.random().toString(36).substring(7);
});
node.enter().append("circle")
.attr('r', 11)
.style('stroke', function(d) {
return d3.rgb(colors(d.name)).darker().toString();
});
//
// links
//
link = svg.selectAll(".link")
.data(jsonlinks);
link.exit().remove();
link.enter().append("line")
.attr("class", "link");
//
// labels
//
label = svg.selectAll(".label")
.data(jsonlinks);
label.exit().remove();
label.enter().append("text")
.attr("class", "label")
.text(function(d) {
var lbl = d.source.name + "_" + d.target.name;
return lbl ;
});
label.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
.attr("y", function(d) {return (d.source.y + d.target.y) / 2;});;
force.start();
}
function removeNode(victim) {
var searchres = findNodeIndex(jsonnodes, victim.name);
if (searchres === null) {
console.log("Node to be removed not found.");
} else {
jsonnodes.splice(searchres, 1);
}
}
function removeLinks(victim) {
var searchres = findFirstLinkIndex(jsonlinks, victim.name);
if (searchres !== null) {
jsonlinks.splice(searchres, 1);
removeLinks(victim);
}
}
// Returns the position/index in node collection of the node with name value name
function findNodeIndex(coll, name) {
if (coll === null)
return null;
for (var i=0; i<coll.length; i++) {
if (coll[i].name === name) {
return i;
}
}
return null;
}
// Returns the position/index of the first link matching the provided node name
function findFirstLinkIndex(coll, name) {
if (coll === null)
return null;
for (var i=0; i<coll.length; i++) {
if ((coll[i].source.name === name) || (coll[i].target.name === name))
return i;
}
return null;
}
I'm seeing a weird behaviour in my D3 application and after hours of trying to figure out what's happening I hope someone can point me at the thing I obviously do wrong.
I have simplified the app down to be very simple and still exhibit the problem. As you'll see it's derived from all the great D3 examples out there. The simple scenario I have an issue with is: select a node (by clicking on it) and, upon hitting the delete key remove said node along with all related links and labels of both the node and the links.
The code pasted below is nearly there since it decreases the number of Nodes and Links exactly as anticipated (given any particular graph) but there is one issue: both the node and link labels are not the correct ones and end up distributed over different circles...
Any idea as to what might be going on would be greatly appreciated!
Code:
var width = 960,
height = 700,
colors = d3.scale.category20();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(.05)
.distance(200)
.charge(-150)
.size([width, height]);
var jsonnodes, jsonlinks;
var node, link, label;
var selected_node = null,
mousedown_node = null,
mousedown_link = null;
d3.json("graph.json", jsondatacallback);
//
// Functions
//
function jsondatacallback(error, json) {
jsonnodes = json.nodes;
jsonlinks = json.links;
force.nodes(jsonnodes)
.links(jsonlinks);
//
// Nodes
//
node = svg.selectAll(".node")
.data(jsonnodes);
node.enter().append("g")
.attr("class", "node")
.on('mousedown', function(d) {
mousedown_node = d;
if (mousedown_node === selected_node)
selected_node = null;
else
selected_node = mousedown_node;
})
.call(force.drag);
node.append("circle")
.attr('r', 11)
.style('stroke', function(d) {
return d3.rgb(colors(d.name)).darker().toString();
});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
//
// Links
//
link = svg.selectAll(".link")
.data(jsonlinks);
link.enter().append("line")
.attr("class", "link");
//
// Labels (for links)
//
label = svg.selectAll(".label")
.data(jsonlinks);
label.enter().append("text")
.attr("class", "label");
label.attr("dx", 12)
.attr("dy", ".35em")
.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
.attr("y", function(d) {return (d.source.y + d.target.y) / 2;})
.text(function(e) {
return Math.random().toString(36).substring(7); ;
});
force.on("tick", function() {
link.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;});
node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
label.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
.attr("y", function(d) {return (d.source.y + d.target.y) / 2;});
});
d3.select(window)
.on("keydown", keydown);
restart();
}
function keydown() {
d3.event.preventDefault();
var lastKeyDown = d3.event.keyCode;
if (!selected_node)
return;
switch (d3.event.keyCode) {
case 8: // backspace
case 46: // delete
if (selected_node) {
removeNode(selected_node);
removeLinks(selected_node);
}
selected_node = null;
restart();
break;
}
}
function restart() {
//
// nodes
//
node = svg.selectAll(".node")
.data(jsonnodes);
node.exit().remove();
node.style('fill', function(d) {
return (d === selected_node) ? d3.rgb(colors(d.name)).brighter().toString() : colors(d.name);
})
.on('mousedown', function(d) {
mousedown_node = d;
if (mousedown_node === selected_node)
selected_node = null;
else
selected_node = mousedown_node;
restart();
});
node.enter().append("g")
.attr("class", "node")
.on('mousedown', function(d) {
mousedown_node = d;
if (mousedown_node === selected_node)
selected_node = null;
else
selected_node = mousedown_node;
});
node.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return Math.random().toString(36).substring(7);
});
node.enter().append("circle")
.attr('r', 11)
.style('stroke', function(d) {
return d3.rgb(colors(d.name)).darker().toString();
});
//
// links
//
link = svg.selectAll(".link")
.data(jsonlinks);
link.exit().remove();
link.enter().append("line")
.attr("class", "link");
//
// labels
//
label = svg.selectAll(".label")
.data(jsonlinks);
label.exit().remove();
label.enter().append("text")
.attr("class", "label")
.text(function(d) {
var lbl = d.source.name + "_" + d.target.name;
return lbl ;
});
label.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
.attr("y", function(d) {return (d.source.y + d.target.y) / 2;});;
force.start();
}
function removeNode(victim) {
var searchres = findNodeIndex(jsonnodes, victim.name);
if (searchres === null) {
console.log("Node to be removed not found.");
} else {
jsonnodes.splice(searchres, 1);
}
}
function removeLinks(victim) {
var searchres = findFirstLinkIndex(jsonlinks, victim.name);
if (searchres !== null) {
jsonlinks.splice(searchres, 1);
removeLinks(victim);
}
}
// Returns the position/index in node collection of the node with name value name
function findNodeIndex(coll, name) {
if (coll === null)
return null;
for (var i=0; i<coll.length; i++) {
if (coll[i].name === name) {
return i;
}
}
return null;
}
// Returns the position/index of the first link matching the provided node name
function findFirstLinkIndex(coll, name) {
if (coll === null)
return null;
for (var i=0; i<coll.length; i++) {
if ((coll[i].source.name === name) || (coll[i].target.name === name))
return i;
}
return null;
}
Share
Improve this question
asked Mar 7, 2014 at 3:28
FrodoFrodo
733 bronze badges
3
- 1 Add a key function to all the data joins, so that a given data object will always be joined to the same on-screen element even if you delete a data object from the middle of the array. – AmeliaBR Commented Mar 7, 2014 at 3:53
- Oh wow, Thanks a lot for the answer and the link; I had missed that part of the D3 doc. I simply added "function(d) {return d.name;}" to the data selection for the nodes and it just worked. It's interesting that mos of the examples I've seen simply rely on the default index-based behaviour. Cheers! – Frodo Commented Mar 7, 2014 at 4:25
- Great. Key functions are only important if you're going to be messing around with your data array order on updates -- but then they are very, very important! I'll add this as a proper answer. – AmeliaBR Commented Mar 7, 2014 at 4:33
1 Answer
Reset to default 13If you are going to be deleting data elements from the middle of your array, you need to specify a key function to the data join so d3 knows which data should go with which element. Otherwise, the data is matched to elements in the order they are found and when there isn't enough data to go around the last element is the one that ends up removed.
- Tutorial on key functions and object constancy
- API and links to more tutorials
Since you're using the name
property of each data element as the identifier for removing elements, that is the logical choice for a data key.
node = svg.selectAll(".node")
.data(jsonnodes, function(d){return d.name;});
/*...*/
link = svg.selectAll(".link")
.data(jsonlinks,
function(d){return d.source.name + "_" + d.target.name;});
/*...*/
label = svg.selectAll(".label")
.data(jsonlinks,
function(d){return d.source.name + "_" + d.target.name;});
本文标签: javascriptD3 update on node removal always remove the last entry in SVG DOMStack Overflow
版权声明:本文标题:javascript - D3 update on node removal always remove the last entry in SVG DOM - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1743904007a2559188.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论