问题
I have a D3 v5 treemap that I show three levels of the hierarchy via a filter. Showing all levels at once would make the map too busy. I'd like to employ a drill down or drill up feature such that when you click on a given rect (either child or parent) that re-sets that rect as the root node and then displays another three levels of the hierarchy from that new root node.
I think I'm getting the new root node data, but I can't seem to get the map to update correctly with the onclick
(the console.log in the exit().remove()
doesn't get called after the onclick). It seems like elements are not exiting correctly, or not getting to the exit().remove()
.
Here is the code:
const treemapLayout = d3.treemap()
.size([1200, 700])
.paddingOuter(16);
d3.json("test.json").then(function(data) {
// update the view
const update = (d) => {
//console.log(d)
let rootNode = d3.hierarchy(d)
console.log(rootNode)
rootNode
.sum(function(d) { return d.value; })
.sort(function(a, b) { return b.height - a.height || b.value - a.value; });
treemapLayout(rootNode);
let nodes = d3.select('svg g')
.selectAll('g')
.data(rootNode.descendants())
nodes
.enter()
.filter(function(d) {
return d.depth < 3;
})
.append('g')
.merge(nodes)
.attr('transform', function(d) {return 'translate(' + [d.x0, d.y0] + ')'})
//nodes
.append('rect')
.attr('width', function(d) { return d.x1 - d.x0; })
.attr('height', function(d) { return d.y1 - d.y0; })
.attr('style', function(d) {
return ('fill:' + d3.interpolateRdYlGn(d.data.health))
})
.on('click', function(d.parent) { console.log(d.data.name); update(d); })
nodes
.append('text')
.attr('dx', 4)
.attr('dy', 14)
.text(function(d) {
return d.data.name;
})
nodes
.attr('style', function(d) { console.log('here'); return d; })
.exit().remove()
};
update(data);
});
Here is the data in a simplified format:
{
"name": "A1",
"health": 0.521,
"children": [
{
"name": "B1",
"health": 0.521,
"children": [
{
"name": "B1-C1",
"health": 0.614,
"children": [
{
"name": "B1-C1-D1",
"health": 0.666,
"children": [
{
"name": "B1-C1-D1-E1",
"value": 30,
"health": 0.8
},
{
"name": "B1-C1-D1-E2",
"value": 35,
"health": 0.5
},
{
"name": "B1-C1-D1-E3",
"value": 20,
"health": 0.7
}
]
},
{
"name": "B1-C1-D2",
"health": 0.45,
"children": [
{
"name": "B1-C1-D2-E1",
"value": 10,
"health": 0.8
},
{
"name": "B1-C1-D2-E1",
"value": 14,
"health": 0.1
}
]
},
{
"name": "B1-C1-D3",
"health": 0.64,
"children": [
{
"name": "B1-C1-D3-E1",
"value": 10,
"health": 0.8
},
{
"name": "B1-C1-D3-E2",
"value": 14,
"health": 0.2
},
{
"name": "B1-C1-D3-E3",
"value": 7,
"health": 0.7
},
{
"name": "B1-C1-D3-E4",
"value": 9,
"health": 0.9
},
{
"name": "B1-C1-D3-E5",
"value": 5,
"health": 0.6
}
]
},
{
"name": "B1-C1-D4",
"value": 2,
"health": 0.7
}
]
},
{
"name": "B1-C2",
"health": 0.45,
"children": [
{
"name": "B1-C2-D1",
"health": 0.45,
"value": 12
}
]
},
{
"name": "B1-C3",
"health": 0.5,
"children": [
{
"name": "B1-C3-D1",
"health": 0.5,
"value": 10
}
]
}
]
}
]
}
回答1:
D3 selections are immutable.
When you do this...
nodes.enter()
.filter(function(d) {
return d.depth < 3;
})
.append('g')
.merge(nodes)
//etc...
... the merge
is not changing what nodes
is, which is just the update selection.
You have to reassign it:
nodes = nodes.enter()
//etc...
Here is your code with that change (in a smaller SVG):
const data = {
"name": "A1",
"health": 0.521,
"children": [{
"name": "B1",
"health": 0.521,
"children": [{
"name": "B1-C1",
"health": 0.614,
"children": [{
"name": "B1-C1-D1",
"health": 0.666,
"children": [{
"name": "B1-C1-D1-E1",
"value": 30,
"health": 0.8
},
{
"name": "B1-C1-D1-E2",
"value": 35,
"health": 0.5
},
{
"name": "B1-C1-D1-E3",
"value": 20,
"health": 0.7
}
]
},
{
"name": "B1-C1-D2",
"health": 0.45,
"children": [{
"name": "B1-C1-D2-E1",
"value": 10,
"health": 0.8
},
{
"name": "B1-C1-D2-E1",
"value": 14,
"health": 0.1
}
]
},
{
"name": "B1-C1-D3",
"health": 0.64,
"children": [{
"name": "B1-C1-D3-E1",
"value": 10,
"health": 0.8
},
{
"name": "B1-C1-D3-E2",
"value": 14,
"health": 0.2
},
{
"name": "B1-C1-D3-E3",
"value": 7,
"health": 0.7
},
{
"name": "B1-C1-D3-E4",
"value": 9,
"health": 0.9
},
{
"name": "B1-C1-D3-E5",
"value": 5,
"health": 0.6
}
]
},
{
"name": "B1-C1-D4",
"value": 2,
"health": 0.7
}
]
},
{
"name": "B1-C2",
"health": 0.45,
"children": [{
"name": "B1-C2-D1",
"health": 0.45,
"value": 12
}]
},
{
"name": "B1-C3",
"health": 0.5,
"children": [{
"name": "B1-C3-D1",
"health": 0.5,
"value": 10
}]
}
]
}]
}
const treemapLayout = d3.treemap()
.size([500, 300])
.paddingOuter(16);
// update the view
const update = (d) => {
//console.log(d)
let rootNode = d3.hierarchy(d)
console.log(rootNode)
rootNode
.sum(function(d) {
return d.value;
})
.sort(function(a, b) {
return b.height - a.height || b.value - a.value;
});
treemapLayout(rootNode);
let nodes = d3.select('svg g')
.selectAll('g')
.data(rootNode.descendants())
nodes
.exit().remove()
nodes = nodes
.enter()
.filter(function(d) {
return d.depth < 3;
})
.append('g')
.merge(nodes)
.attr('transform', function(d) {
return 'translate(' + [d.x0, d.y0] + ')'
})
//nodes
nodes.append('rect')
.attr('width', function(d) {
return d.x1 - d.x0;
})
.attr('height', function(d) {
return d.y1 - d.y0;
})
.attr('style', function(d) {
return ('fill:' + d3.interpolateRdYlGn(d.data.health))
})
.on('click', function(d) {
console.log(d.data.name);
update(d);
})
nodes
.append('text')
.attr('dx', 4)
.attr('dy', 14)
.text(function(d) {
return d.data.name;
})
};
update(data);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="300">
<g></g>
</svg>
来源:https://stackoverflow.com/questions/57470346/enter-update-and-exit-selections-in-a-treemap