Moving back in with the parents...

4,266 views
Skip to first unread message

Ziggy Jonsson

unread,
Oct 21, 2011, 11:45:22 AM10/21/11
to d3-js
In D3 it's very easy go traverse down to children in a linked
fashion. But is there an easy way to go from a selection of nodes to
the parent nodes?

Here is what I am thinking:

svg.append("table")
.append("tr")
.append("td")
.text("This is a title")
.parent()
.append("td")
.text(function(d) { return d.value })

Alternatively, with "sudnodes" where append returns the parent not the
child:

svg.append("table")
.append("tr")
.append(subnode("td").text("this is a title"))
.append(subnode("td").text(function(d) { return d.value}))

I realize I can do this easily by declaring variables for each parent
and start a new line whenever I need to move up, but chained approach
for creating a full tree would look cool. I also realize that for a
single node I can go node[0][0].parentNode, however this does not work
for selections, unless I do loops myself.

Z

Peter Rust

unread,
Oct 21, 2011, 9:07:10 PM10/21/11
to d3-js
In the d3 workshop at SVG Open yesterday, Mike mentioned that the lack
of a method to get back to a previous selection (like jQuery's end())
is intentional, as this coding style usually leads to hard-to-follow
code. The alternative (creating variables with clear, descriptive
names) usually leads to easy-to-follow code. I would add that using
variables also makes your code easier to debug.

From your example, it looks like one of your motivations may be the
desire to do all (or a significant portion) of your DOM generation
with d3. For this use-case, I agree that having a parent() method
makes it more declarative and natural to build a nested DOM structure.

However, I would argue that it would be even *more* declarative and
natural to use a markup-centric approach, such as a static HTML file,
HTML templating or HTML string concatenation -- and then to do your d3
injection on top of that. I think you'll find that a more markup-
centric approach will make it easier for others to quickly understand
your DOM structure -- and for you to quickly remember it when you're
revisiting it 6 months later!

-- Peter Rust
Developer, Cornerstone Systems

Ziggy Jonsson

unread,
Oct 21, 2011, 10:02:42 PM10/21/11
to d3-js
I agree with you, however I would like to make a distinction between
the "first method" and the "second method". Going "up a level" in a
chained fashion might be confusing and tough to debug if used
inappropriately.

However, being able to enter a stream of children into a parent node
function, and then continue the chain where the parent left off would
be much less confusing and allow the code to be highly structural.

I guess I am tired of creating an endless stream of unnecessary (or so
it seems) temporary variables... It never seems elegant and casts a
shadow on my otherwise successful day... and yes you are right, most
of my D3 related projects build the entire DOM in D3 :)

Mike Bostock

unread,
Oct 21, 2011, 10:18:29 PM10/21/11
to d3...@googlegroups.com
Yes, it's intentional that there's no way to go back up the hierarchy
with method chaining: you can only descend. Thank you, Peter, for
summarizing my earlier remarks!

There are two other technical reasons for not supporting ascension via
method chaining.

First, any subselection would have to keep a reference to the parent
selection that generated it, which is potentially a bit of extra
baggage. It might also be possible for the two to get out of sync.

Second, it gets more complicated with grouping. When you subselect
with selectAll, you group a selection: for each element in the current
selection, you select an array of child elements, and each of these
arrays becomes a separate group. Separate groups are needed to bind
different hierarchical data (such as multi-dimensional arrays) to each
group. It's also handy for assigning indexes within a group rather
than globally. If you want to go back up the hierarchy by selecting a
parent node, you have to decide whether you want to select the parent
for each element (in which case the same parent node may be selected
multiple times for siblings), or for each group.

It's just easier to use variables. You don't have to create a variable
for everything; only those cases where you want to append multiple
siblings, or bubble back up to a parent after modifying children. And
yes, I believe this also encourages you to make your code more
readable by naming what you are doing. :)

Mike

Message has been deleted

Ziggy Jonsson

unread,
Oct 22, 2011, 6:05:53 PM10/22/11
to d3-js
Thanks Mike and Peter, you have convinced me that jumping up to
parents in a chained fashion is a bad idea. I guess I didn't think
it through fully.

I still think the possibility of creating children and passing on the
parents is relatively efficient. However maybe it just complicates the
already simple way to manipulate the DOM.

X3D example without any temporary variables:

svg.append("v:shape")
.childAppend(d3.element("box"))
.childAppend(d3.element("appearance")
.append("material")
.attr("diffuseColor","red")
)

Mike Bostock

unread,
Oct 22, 2011, 6:24:22 PM10/22/11
to d3...@googlegroups.com
Inserting siblings is a possibility—so you can go down, and sideways,
but still not up. :) I'd like to base it on insertAdjacentHTML:

http://www.w3.org/TR/html5/apis-in-html-documents.html#insertadjacenthtml

For example, perhaps there's an "adjoin" operator which takes one of
the four placement options. In this case, we want to insert a sibling
appearance element after the box element:

svg.append("shape")
.append("box")
.adjoin("appearance", "afterend")
.append("material")
.attr("diffuseColor", "red");

The name "adjoin" might be a bit awkward, but I think it's important
it's not "append" or "insert" so that it sticks out when reading the
structure. Of course, using local variables makes the structure even
more obvious. ;)

Mike

Ziggy Jonsson

unread,
Mar 31, 2012, 3:11:25 PM3/31/12
to d3...@googlegroups.com
After working with D3 for quite some time I wanted to update my wishlist regarding selections.  

1) Maintaining the original scope in a typical select/data chain with enter/exit:

svg.selectAll(".line1").data(data)
    .enter(function(d) { d.append("td").attr("class","line1")})
    .exit(function(d) { d.remove()})
   .attr("x",.....)
   ....etc

.. meaning that if a function is passed to exit or enter, the chain continues with the original scope (passing the subselection to the attached function).  If no arguments are passed to enter/exit the scope of the chain is changed (current mechanism).  This is somewhat similar to attr/style, i.e. when called with an argument the value is set and function returns the original chain, otherwise returning the value being requested  I hope this would create interesting options to users as well as being backwards compatible.

2) Forking selections
I guess the original idea on adjoining (previous post) might be better utilized by a fork of the selection (instead of an append, insert etc). 

So my original idea of:


svg.append("v:shape") 
    .childAppend(d3.element("box")) 
    .childAppend(d3.element("appearance") 

           .append("material") 
                .attr("diffuseColor","red") 
          )  

would maybe better be presented as:

svg.append("v:shape")
     .fork(function(d) { d.append("box")})
     .fork(function(d) { d.append("appearance").append("material").attr("diffuseColor","red")

... this should also be backwards comparable, but allow users to create a very complex structure in a chainable fashion without local variables.

Mike Bostock

unread,
Mar 31, 2012, 3:35:02 PM3/31/12
to d3...@googlegroups.com
> 2) Forking selections

This is already available as selection.call:

https://github.com/mbostock/d3/wiki/Selections#wiki-call

For example:

d3.select("body")
.call(function(body) { body.append("h1").text("hello"); })
.call(function(body) { body.append("p").text("world"); });

Personally, I find deeply-nested method chaining a bit hard to follow
and prefer local variables. But call is there if you want to use it.
You can also pass additional arguments, e.g.,

d3.select("body")
.call(heading, "hello")
.call(paragraph, "world");

function heading(parent, text) {
parent.append("h1").text(text);
}

function paragraph(parent, text) {
parent.append("p").text(text);
}

Mike

Ziggy Jonsson

unread,
Mar 31, 2012, 5:04:54 PM3/31/12
to d3...@googlegroups.com
Thanks Mike.  That was obvious in hindsight :)

My personal preference would be to have enter() and exit() like the "call()" but with a particular relevant context (previous email) but I guess this should work: 

svg.selectAll(".line1").data(data)
    .call(function(d) { d.enter().append("td").attr("class","line1")})
    .call(function(d) { d.exit().remove()})
   .attr("x",.....)
   ....etc

(and so the fight for liberty against local variables continues)
Reply all
Reply to author
Forward
0 new messages