So yesterday I asked a question , the answer of which gave me some useful insight into how powerful d3's functions can be.
But when trying to apply it myself, it produced strange results. I'm trying to replace the circles of the patent suits example with various icons, but even though console.log
shows that the function is receiving the paths and names of all images correctly, it ends up with just one icon for all nodes. Besides that, I'm surprised to see so many console.log
outputs when I have just six nodes.
The code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
opacity: 0.5;
}
#linkup {
fill: green;
}
.link.linkup {
stroke: green;
}
.link.unknownlink {
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>
<script src="d3/d3.v3.min.js"></script>
<script>
var links = [
{source: "Device1", target: "Device20", type: "linkup", icon: "images/workstation.jpg"},
{source: "Device1", target: "Device5", type: "linkup", icon: "images/router.jpg"},
{source: "Device2", target: "Device8", type: "linkdown", icon: "images/workstation.jpg"},
{source: "Device3", target: "Device8", type: "linkdown", icon: "images/workstation.jpg"}
];
var nodes = {};
// 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});
});
var width = 960, height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))//prolly giving values to the nodes
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["linkdown", "linkup", "unknownlink"])
.enter().append("marker")//arrowmarker type of markers
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")//the viewing area for every marker I guess...
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");//this is for the arrow
var path = svg.append("g").selectAll("path")
.data(force.links())//console.log(force.links());//outputs [Object { source={...}, target={...}, type="linkup"}, Object { source={...}, target={...}, type="linkup"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="unknownlink"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="unknownlink"}, Object { source={...}, target={...}, type="unknownlink"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="unknownlink"}, Object { source={...}, target={...}, type="unknownlink"}, Object { source={...}, target={...}, type="unknownlink"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="unknownlink"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}, Object { source={...}, target={...}, type="linkdown"}]
.enter().append("path")//add a standard svg path
.attr("class", function(d) { return "link " + d.type; })//this is where the linestyle is applied
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });//this is where the arrow end style is applied
var circle = svg.append("g").selectAll("path")
.data(force.nodes())
.enter().append("g")
.call(force.drag); //assigning the capability of dragging the circle
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text") //adding the svg text element
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; }); //getting the name from nodes which was assigned to force
circle.selectAll("image")
.data(links)
.enter()
.append("image")
.attr("xlink:href", function(d) {console.log(d.icon);return d.icon;})
//.attr("xlink:href", "images/workstation.jpg")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
circle.on("mousedown", function(d) { d.fixed = true; });//make the node sticky
circle.on("dblclick", function(d) { d.fixed = false; });//make the node un-sticky
// 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>
My question: ...is not just about what am I doing wrong in .attr("xlink:href", function(d) {console.log(d.icon);return d.icon;}
. My question is also that I want to properly understand how this selection works. How is it that selectAll("text")
and .enter().append("text")
was able to append every individual piece of text, but my image attachment didn't work properly? How exactly do these data()
, enter()
and selectAll()
functions manage to do this job of selecting object instances individually and applying properties to them?
You're seeing more messages than you're expecting and not the right images because you're in fact appending many more elements than you think. The key parts of the code are these:
var circle = svg.append("g").selectAll("path")
.data(force.nodes())
.enter().append("g")
circle.selectAll("image")
.data(links)
.enter()
.append("image")
After the first block of code, circles
holds a selection that contains all the newly appended g
elements -- that is, one for each node. By doing a .selectAll(...).data(...)
on this selection, you're making a nested selection -- for each element in the selection you're selecting descendant elements and binding data. So for each g
element, you're binding data to all its descendant image
elements. The subselection is per element of the original selection, not for the original selection as a whole.
As a result, you're appending, to each g
element, links.length
image
element. This is because you're binding links
as the data. So all g
elements had the same image
elements in the same order (as the same data was bound to each), but only the first is shown in SVG.
To fix this, I've done two things. First, as the images are for the nodes and not the links, I've modified your code to generate the nodes from the links slightly to include the information which icon to use in the nodes.
To actually append the images, you don't need another selection, the following code does it.
circle.append("image")
.attr("xlink:href", function(d) {console.log(d.icon);return d.icon;})
This is telling D3 to append a new image
element to each element in the current selection (ie the new g
elements).
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.