Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
174 views
in Technique[技术] by (71.8m points)

javascript - D3, nested appends, and data flow

I'm in the process of finally learning D3, and I stumbled upon a problem that I haven't been able to find an answer to. I'm not certain if my question is because I'm not thinking idiomatically with the library, or if it is because of a procedure that I am currently unaware of. I should also mention that I've only started doing web-related things in June, so I'm fairly new to javascript.

Say that we're building a tool that gives a user a list of foods with respective images. And lets add on the additional constraint that each list item needs to be labeled by a unique ID so that it can be linked to another view. My first intuition to solve this is to create a list of <div>'s each with their own ID, where each div has its own <p> and <img>. The resulting HTML would look something like:

<div id="chocolate">
  <p>Chocolate Cookie</p>
  <img src="chocolate.jpg" />
</div>
<div id="sugar">
  <p>Sugar Cookie</p>
  <img src="sugar.jpg" />
</div>

The data for this tool is in a JSON array, where an individual JSON looks like:

{ "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }

Is there a way to do generate the HTML in one fell swoop? Starting with a base case of adding a div, the code might look something like:

d3.select(containerId).selectAll('div')                                                          
   .data(food)
   .enter().append('div')
   .attr('id', function(d) { return d.label; });

Now, what about adding a <div> with a <p> in it? My original thought was to do something like:

d3.select(containerId).selectAll('div')                                                          
   .data(food)
   .enter().append('div')
   .attr('id', function(d) { return d.label; })
       .append('p').text('somethingHere');

But, I see two problems with this: (1) how do you get the data from the div element, and (2) how can you append multiple children to the same parent in one declarative chain? I can't think of a way to make the third step where I would append on the img.

I found mention of nested selection on another post, which pointed to http://bost.ocks.org/mike/nest/. But is nested selection, and therefore breaking apart the appends into three chunks, appropriate/idiomatic for this situation? Or is there actually a well-constructed way to form this structure in one chain of declarations? It seems like there might be a way with subselections mentioned on https://github.com/mbostock/d3/wiki/Selections, but I'm not familiar enough with the language to test that hypothesis.

From a conceptual level, these three objects (div, p, and img) are treated more like one group rather than separate entities, and it would be nice if the code reflected that as well.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You cannot add multiple child elements within one chained command. You will need to save the parent selection in a variable. This should do what you want:

var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
        { "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];

var diventer = d3.select("body").selectAll("div")
    .data(data)
  .enter().append("div")
    .attr("id", function(d) { return d.label; });

diventer.append("p")
    .text(function(d) { return d.text; });

diventer.append("img")
    .attr("src", function(d) { return d.img; });?

See working fiddle: http://jsfiddle.net/UNjuP/

You were wondering how a child element like p or img, gets access to the data that is bound to its parent. The data is inherited automatically from the parent when you append a new element. This means that the p and img elements will have the same data bound to them as the parent div.

This data propagation is not unique for the append method. It happens with the following selection methods: append, insert, and select.

For example, for selection.append:

selection.append(name)

Appends a new element with the specified name as the last child of each element in the current selection. Returns a new selection containing the appended elements. Each new element inherits the data of the current elements, if any, in the same manner as select for subselections. The name must be specified as a constant, though in the future we might allow appending of existing elements or a function to generate the name dynamically.

Feel free to ask about the details if something is not clear.


EDIT

You can add multiple child elements without storing the selection in a variable by using the selection.each method. You can then also directly access the data from the parent:

var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
        { "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];

d3.select("body").selectAll("div")
    .data(data)
  .enter().append("div")
    .attr("id", function(d) { return d.label; })
    .each(function(d) {
        d3.select(this).append("p")
          .text(d.text);
        d3.select(this).append("img")
          .attr("src", d.img);
    });

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...