Yes, you can use subselections.
Here is a function to generate the DOM you want:
function show(arr) {
//bind data to divs
parent = d3.select("body").selectAll("div")
.data(arr);
//enter-update-exit pattern used to create divs
parent.exit().remove();
parent.enter().append("div");
parent.style({
"top" : function (d) { return d.y} ,
"left" : function (d) { return d.x }
});
//basic subselection bind
strong = parent.selectAll("strong")
.data(function(d) { return [d.id]});
//enter-update-exit
strong.exit().remove();
strong.enter().append("strong")
strong.text(String)
// subselection bind that handles missing attributes
span = parent.selectAll("span")
.data(function(d) { return d.a ? [d.a] : [] });
//enter-update-exit
span.exit().remove();
span.enter().append("span")
span.text(String)
}
My first call to show()
data1 = [
{id: 1, x:10, y:20, a: 123},
{id: 2, x:20, y:12},
{id: 3, x:15, y:15},
];
show(data1);
appends this to the DOM:
<div style="top: 20px; left: 10px;">
<strong>1</strong>
<span>123</span>
</div>
<div style="top: 12px; left: 20px;">
<strong>2</strong>
</div>
<div style="top: 15px; left: 15px;">
<strong>3</strong>
</div>
I then call it again with
data2 = [
{id: 1, x:10, y:20},
{id: 2, x:20, y:12, a: 123}
]
show(data2);
and my DOM elements become
<div style="top: 20px; left: 10px;">
<strong>1</strong>
</div>
<div style="top: 12px; left: 20px;">
<strong>2</strong>
<span>123</span>
</div>
The block that binds data to the divs is straightforward. No comment required.
parent = d3.select("body").selectAll("div")
.data(arr);
The blocks that create the elements (div, strong, span) all are a straightforward application of the enter-update-exit idiom in d3. We do the exit, then the enter, and the update last--that's a common variation on the pattern.
parent.exit().remove(); //exit
parent.enter().append("div"); //enter
parent.style({
"top" : function (d) { return d.y} ,
"left" : function (d) { return d.x }
}); //update
The subselections are where the magic happens (see the documentation for selection.data). The function you pass to data will receive the element in the array that its parent div is bound to (e.g {id: 2, x:20, y:12}). The function returns whatever element we want to bind our subselection to (which must be an array). For the strong elements, we are just grabbing the id and wrapping it in an array.
// parent datum {id: 2, x:20, y:12} becomes child data [2]
strong = parent.selectAll("strong")
.data(function(d) { return [d.id]});
For the span element, we wrap the value of the attribute in the array when it exists, and just return an empty array when it does not.
// parent datum {id: 1, x:10, y:20, a: 123} becomes [123]
// parent datum {id: 2, x:20, y:12} becomes []
span = parent.selectAll("span")
.data(function(d) { return d.a ? [d.a] : [] });