forked from mbostock's block: D3 Source Treemap
I wanted to demonstrate how to add a brush for each cell in a treemap with d3v4
, since today I used a similar but slightly more robust method for a win at work as mentioned in this tweet.
Great win at work today with #d3js and #rstats to make interactive brushable treemap with real-time discussion for reallocation of a client portfolio; thx @mbostock!
— timelyportfolio (@timelyportfolio) December 20, 2017
I hope it helps someone along the way. For reference, I also added click on the cell that would add or delete a brush for each cell. With just a click, the cell would highlight and be 100%. The added brush allows partial selection. Unclick deletes the brush and sets to 0%.
The treemap I used had far fewer cells than this one from the d3 source code. Regardless, I think a bar chart with brushes (see example) might be a better UI for this assuming a limited number of bars.
Thanks so much to Mike Bostock for his amazing generous contribution that does not get nearly enough recognition or money.
This treemap shows the file size in bytes of D3 4.4.0’s source code. Click on any cell to view the corresponding source.
forked from timelyportfolio's block: D3 Source Treemap (forked for split algo)
xxxxxxxxxx
<style>
svg {
font: 10px sans-serif;
}
a:hover tspan:first-child {
text-decoration: underline;
}
tspan:last-child {
font-size: 9px;
fill-opacity: 0.7;
}
.brush .handle {
fill: #888;
height: 3px;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="selected" width="960" height="100">
<rect width="50"></rect>
</svg>
brush in the cells in the treemap below...
<svg id="tree" width="960" height="960"></svg>
<script>
var svg = d3.select("svg#tree"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var format = d3.format(",d");
var treemap = d3.treemap()
.size([width, height])
.round(true)
.padding(1)
.tile(d3.treemapBinary);
d3.csv("d3.csv", function(d) {
d.size = +d.size;
return d;
}, function(error, data) {
if (error) throw error;
var root = d3.stratify()
.id(function(d) { return d.path; })
.parentId(function(d) { return d.path.substring(0, d.path.lastIndexOf("/")); })
(data)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.height - a.height || b.value - a.value; });
treemap(root);
// axis for selected scale
d3.select("svg#selected").append("g")
.call(
d3.axisBottom().scale(
d3.scaleLinear().range([0,960]).domain([0,root.value])
)
)
.attr("transform", "translate(0, 50)")
// using g for the cells will allow us the easiest way to contain the brush
// that we will add for each cell
var cell = svg.selectAll("g")
.data(root.leaves())
.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; });
cell.append("rect")
.attr("id", function(d) { return d.id; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("fill", function(d) { var a = d.ancestors(); return color(a[a.length - 2].id); });
cell.append("clipPath")
.attr("id", function(d) { return "clip-" + d.id; })
.append("use")
.attr("xlink:href", function(d) { return "#" + d.id; });
var label = cell.append("text")
.attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; });
label.append("tspan")
.attr("x", 4)
.attr("y", 13)
.text(function(d) { return d.data.path.substring(d.data.path.lastIndexOf("/") + 1, d.data.path.lastIndexOf(".")); });
label.append("tspan")
.attr("x", 4)
.attr("y", 25)
.text(function(d) { return format(d.value); });
// now add a brush for each cell
// but first make a very simple state object
var selected = {}
// but second define a function to handle brushing
function brushing(node) {
var sel = d3.event.selection || 0
var pct = (sel[1] - sel[0]) / (node.y1 - node.y0)
selected[node.id] = pct * node.value
updateSelected()
}
cell.append("g")
.classed("brush", true)
.each(function(node) {
var height = node.y1 - node.y0
var width = node.x1 - node.x0
d3.select(this)
.call(
d3.brushY()
// this will limit the brush to the cell g
.extent([[0,0],[width, height]])
.on("end", brushing)
)
})
function updateSelected() {
var total = d3.sum(d3.entries(selected).map(function(d){return d.value}))
d3.select("svg#selected").select("rect")
.attr("height", 50)
.attr("width", total / root.value * 960) // cheap scale :)
}
});
</script>
https://d3js.org/d3.v4.min.js