Reputation: 592
I'm to display a grouping of groups of data using d3js. The nested selection does seem to see the update. How can I get the inner groupings to update?
I've created this (http://jsfiddle.net/f0m574wp/17/) fiddle.
I've got some nested data that my application retrieves asynchronously and constructs and displays as data is coming in.
dat = [{
key: 'asdf',
values:[{
key:'1',
value:'hello'
},{
key:'2',
value:''
}]
},{
key:'xyz',
values:[{
key:'1',
value:'foo'
},{
key:'3',
value:'bar'
}]
}];
As the data object is updated I want to re-run the display function to update the visualization.
function display(mydata){
//let x = mydata;
let x = JSON.parse(JSON.stringify(mydata));
console.log('x', x);
let root = d3.select('div');
// create the outer groupings
let outer = root.selectAll('div.outer').data(x, d=>d.key);
let outerEnter = outer.enter()
.append('div')
.classed('outer', true)
.attr('id', d=>d.key);
// add a heading and body to contain data
outerEnter.append('div').classed('heading', true).append('h1');
outerEnter.append('div').classed('body', true);
outer.exit().remove();
// get prevoius and existing outer groups
let outerall = outerEnter.merge(outer);
let headings = outerall.selectAll('div.heading');
let bodies = outerEnter.selectAll('div.body')
headings.selectAll('h1').text(d=>d.key); // update outer group heading
//lets work with the inner arrays of data
let innerGroup = bodies.selectAll('div.mid')
.data(d=>{ console.log(d); return d.values}, k=>k.key);
let innerGpEnter = innerGroup.enter()
.append('div')
.classed('mid', true);
let innerGpAll = innerGpEnter.merge(innerGroup);
// inner heading
let inner = innerGpAll.selectAll('h4.inner')
//.call(function(sel){console.log(sel.nodes());})
.data(d=>{ console.log(d); return [d];}, k=>k.key);
let innerEnter = inner.enter()
.append('h4')
.classed('inner', true);
innerEnter.merge(inner).text(k=>{console.log(k); return k.key});
// inner detail
let p = innerGpAll.selectAll('p.inner').data(d=>[d], k=>k.key);
let pEnter = p.enter().append('p').classed('inner', true);
pEnter.merge(p).text(v=>v.value);
}
The problem seems to be with the inner data binding :
//lets work with the inner arrays of data
let innerGroup = bodies.selectAll('div.mid')
.data(d=>{ console.log(d); return d.values}, k=>k.key);
It does not seem to update the inner binding as data changes.
Upvotes: 1
Views: 64
Reputation: 102174
The main problem here is the use of two chained selectAll
. You have...
let bodies = outerEnter.selectAll('div.body');
... followed by...
let innerGroup = bodies.selectAll('div.mid').etc...
, which gives us this:
let innerGroup = outerEnter.selectAll('div.body').selectAll('div.mid').etc...
Firstly, you should choose outerall
, not outerEnter
, since outerall
is the update selection. But the main issue here is the chained selectAll
s: there is no data binding after the first one, just after the second one. Because of that the data is not carried to the inner selections.
It's important having in mind that, unlike select
, selectAll
doesn't propagate the data. Have a look at this table I made:
Method | select() | selectAll() |
---|---|---|
Selection | selects the first element that matches the selector string | selects all elements that match the selector string |
Grouping | Does not affect grouping | Affects grouping |
Data propagation | Propagates data | Doesn't propagate data |
Pay attention to the propagates data versus doesn't propagate data.
Solution:
Let's make that a single selectAll
, followed by the data
method:
let innerGroup = outerall.selectAll('div.mid').etc...
Here is your code with that change:
dat = [{
key: 'asdf',
values: [{
key: '1',
value: 'hello'
}, {
key: '2',
value: ''
}]
}, {
key: 'xyz',
values: [{
key: '1',
value: 'foo'
}, {
key: '3',
value: 'bar'
}]
}];
function display(mydata) {
//let x = mydata;
let x = JSON.parse(JSON.stringify(mydata));
console.log('x', x);
let root = d3.select('div');
// create the outer groupings
let outer = root.selectAll('div.outer').data(x, d => d.key);
let outerEnter = outer.enter()
.append('div')
.classed('outer', true)
.attr('id', d => d.key);
// add a heading and body to contain data
outerEnter.append('div').classed('heading', true).append('h1');
outerEnter.append('div').classed('body', true);
outer.exit().remove();
// get prevoius and existing outer groups
let outerall = outerEnter.merge(outer);
let headings = outerall.selectAll('div.heading');
headings.selectAll('h1').text(d => d.key); // update outer group heading
//lets work with the inner arrays of data
let innerGroup = outerall.selectAll('div.mid')
.data(d => {
console.log(d);
return d.values
}, k => k.key);
let innerGpEnter = innerGroup.enter()
.append('div')
.classed('mid', true);
let innerGpAll = innerGpEnter.merge(innerGroup);
// inner heading
let inner = innerGpAll.selectAll('h4.inner')
//.call(function(sel){console.log(sel.nodes());})
.data(d => {
console.log(d);
return [d];
}, k => k.key);
inner.exit().remove();
let innerEnter = inner.enter()
.append('h4')
.classed('inner', true);
innerEnter.merge(inner).text(k => {
console.log(k);
return k.key
});
// inner detail
let p = innerGpAll.selectAll('p.inner').data(d => [d], k => k.key);
p.exit().remove();
let pEnter = p.enter().append('p').classed('inner', true);
pEnter.merge(p).text(v => v.value);
}
display(dat);
setTimeout(function() {
console.log('Send New data');
//dat[0].key = 'abc';
dat[0].values[1].value = 'good bye';
dat[1].values.push({
key: 2,
value: 'baz'
});
display(dat);
}, 3000)
* {
margin: 2px;
padding: 2px;
border: 0;
}
body {
background: #ffd;
}
.outer {
border: 1px solid green;
width:
}
.mid {
border: 1px solid red;
}
.inner {
border: 1px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div>
</div>
Solution 2:
According to your comment, you need to keep that HTML structure. In that case, use select
:
let bodies = outerall.select('div.body');
You can safely use select
here because you're appending just one <div>
with a class body
.
Here is the new snippet:
dat = [{
key: 'asdf',
values: [{
key: '1',
value: 'hello'
}, {
key: '2',
value: ''
}]
}, {
key: 'xyz',
values: [{
key: '1',
value: 'foo'
}, {
key: '3',
value: 'bar'
}]
}];
function display(mydata) {
//let x = mydata;
let x = JSON.parse(JSON.stringify(mydata));
console.log('x', x);
let root = d3.select('div');
// create the outer groupings
let outer = root.selectAll('div.outer').data(x, d => d.key);
let outerEnter = outer.enter()
.append('div')
.classed('outer', true)
.attr('id', d => d.key);
// add a heading and body to contain data
outerEnter.append('div').classed('heading', true).append('h1');
outerEnter.append('div').classed('body', true);
outer.exit().remove();
// get prevoius and existing outer groups
let outerall = outerEnter.merge(outer);
let headings = outerall.selectAll('div.heading');
let bodies = outerall.select('div.body');
headings.selectAll('h1').text(d => d.key); // update outer group heading
//lets work with the inner arrays of data
let innerGroup = bodies.selectAll('div.mid')
.data(d => {
console.log(d);
return d.values
}, k => k.key);
let innerGpEnter = innerGroup.enter()
.append('div')
.classed('mid', true);
let innerGpAll = innerGpEnter.merge(innerGroup);
// inner heading
let inner = innerGpAll.selectAll('h4.inner')
//.call(function(sel){console.log(sel.nodes());})
.data(d => {
console.log(d);
return [d];
}, k => k.key);
inner.exit().remove();
let innerEnter = inner.enter()
.append('h4')
.classed('inner', true);
innerEnter.merge(inner).text(k => {
console.log(k);
return k.key
});
// inner detail
let p = innerGpAll.selectAll('p.inner').data(d => [d], k => k.key);
p.exit().remove();
let pEnter = p.enter().append('p').classed('inner', true);
pEnter.merge(p).text(v => v.value);
}
display(dat);
setTimeout(function() {
console.log('Send New data');
//dat[0].key = 'abc';
dat[0].values[1].value = 'good bye';
dat[1].values.push({
key: 2,
value: 'baz'
});
display(dat);
}, 3000)
* {
margin: 2px;
padding: 2px;
border: 0;
}
body {
background: #ffd;
}
.outer {
border: 1px solid green;
width:
}
.mid {
border: 1px solid red;
}
.inner {
border: 1px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div>
</div>
Upvotes: 3