简体   繁体   中英

d3.js : Want to understand the concept of applying individual properties to elements via a single command

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.

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