Back in the first tutorial, there was a short example involving sugar, spice, and appending nodes. Let's revisit it, but as a data-driven example using what we've learned in previous lessons:
<style type='text/css'>
.thing {
border: 1px solid black;
margin: 5px;
padding: 5px;
width: 200px;
}
.thing.nice {
background: #dfd;
}
.thing.icky {
background: #fdd;
}
</style>
<div id='chart'></div>
var things = [
{name: 'Sugar', isNice: true},
{name: 'Spice', isNice: true},
{name: 'Toe Fungus', isNice: false},
];
d3.select('#chart').selectAll('div.thing')
.data(things).enter()
.append('div')
.classed({
'thing': true,
'nice': function(d) { return d.isNice; },
'icky': function(d) { return !d.isNice; },
})
.text(function(d) { return d.name; });
classed
operator.
This is a shorthand for accessing the classList
property on nodes (or an equivalent if the browser is old). Usually,
setting the class
through an attr
call is
enough, when setting multiple independent classes based on data it
proves convenient.
Now, we can't leave this as-is – our viewer might be colorblind! By adding a little description to each div, we can help set that right. We can easily add a
description
field
to each of our data objects, but how to get that into a new div as
a child of the thing
?
With append
, of course!
<style type='text/css'>
.thing {
border: 1px solid black;
margin: 5px;
padding: 5px;
width: 200px;
}
.thing .description {
margin-left: 1em;
color: #666;
}
.thing.nice {
background: #dfd;
}
.thing.icky {
background: #fdd;
}
</style>
<div id='chart'></div>
var things = [
{name: 'Sugar', isNice: true, description: 'A tasty, sweet thing.'},
{name: 'Spice', isNice: true, description: 'A tasty, savory thing.'},
{name: 'Toe Fungus', isNice: false, description: 'A not so tasty, pungent thing.'},
];
var things = d3.select('#chart').selectAll('div.thing')
.data(things).enter()
.append('div')
.classed({
'thing': true,
'nice': function(d) { return d.isNice; },
'icky': function(d) { return !d.isNice; },
})
.text(function(d) { return d.name; });
things.append('div')
.attr('class', 'description')
.text(function(d) { return d.description; });
div.description
nodes that were
appended never had .data
called but somehow still have
data
bound (unless the nodes were instead touched
by
His Noodly Appendange, but I doubt that). I'm going to go out
on a limb here and say it isn't magic. Rather pedestrian, and
expected, behavior, sadly. D3 automatically propagates
data
from parent nodes to child nodes. Whenever you
use append
, the new node
inherits the bound data from its parent. We can go deeper, even:
<div id='chart'></div>
var nums = [5, 2, 8];
var numDivs = d3.select('#chart').selectAll('div.num')
.data(nums).enter()
.append('div')
.attr('class', 'num')
.text(function(d, i) { return 'First-level! data:' + d + ', index: ' + i; });
var subNumDivs = numDivs.append('div')
.text(function(d, i) { return ' Second-level! data:' + d + ', index: ' + i; });
subNumDivs.append('div')
.text(function(d, i) { return ' THIRD-level! data:' + d + ', index: ' + i; });
data
of the parent, the
index
is also inherited.