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.