A zoomable treemap, with dynamic width for mobile, and wrapped texts.
The code has been ported to D3v4, and humbly improved from the original Jacques Jahnichen's Block.
The data visualizes the income provisional budget for the city of Rome, as of year 2016.
This gist is part of the OpenBilanci project.
Modifications and improvements include: colors are not used, breadcrumbs have been added to the navigation bar, titles have been added to the svg rects (popover effect on some browser)
forked from guglielmo's block: d3.js v4 - Zoomable treemap
xxxxxxxxxx
<meta charset="utf-8">
<style>
#chart {
max-width: 100%;
overflow:auto;
}
text {
pointer-events: none;
}
.grandparent text {
font-weight: bold;
}
rect {
stroke: #fff;
stroke-width: 1px;
}
rect.parent,
.grandparent rect {
stroke-width: 3px;
}
.grandparent:hover rect {
fill: grey;
}
.children rect.parent,
.grandparent rect {
cursor: pointer;
}
.children rect.child {
opacity: 0;
}
.children rect.parent {
}
.children:hover rect.child {
opacity: 1;
stroke-width: 1px;
}
.children:hover rect.parent {
opacity: 0;
}
.legend {
margin-bottom:8px !important;
}
.legend rect {
stroke-width: 0px;
}
.legend text {
text-anchor: middle;
pointer-events: auto;
font-size: 13px;
font-family: sans-serif;
fill: black;
}
.form-group {
text-align:left;
}
.textdiv {
font-family: "Open Sans",Helvetica,Arial,sans-serif;
font-size: 14px;
padding: 7px;
overflow: none;
}
.textdiv .title {
font-size: 102%;
font-weight: bold;
margin-top: 8px;
font-size:11px !important;
}
.textdiv p{
line-height: 13px;
margin:0 0 4px !important;
padding:0px;
font-size:10px !important;
}
</style>
<p id="chart"></p>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
/**
* Interactive, zoomable treemap, using D3 v4
*
* A port to D3 v4 of Jacques Jahnichen's Block, using the same budget data
* see: https://bl.ocks.org/JacquesJahnichen/42afd0cde7cbf72ecb81
*
* Author: Guglielmo Celata
* Date: sept 1st 2017
**/
var el_id = 'chart';
var obj = document.getElementById(el_id);
var divWidth = obj.offsetWidth;
var margin = {top: 30, right: 0, bottom: 20, left: 0},
width = divWidth -25,
height = 600 - margin.top - margin.bottom,
formatNumber = d3.format(","),
transitioning;
// sets x and y scale to determine size of visible boxes
var x = d3.scaleLinear()
.domain([0, width])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, height])
.range([0, height]);
var treemap = d3.treemap()
.size([width, height])
.paddingInner(0)
.round(false);
var svg = d3.select('#'+el_id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("shape-rendering", "crispEdges");
var grandparent = svg.append("g")
.attr("class", "grandparent");
grandparent.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top)
.attr("fill", '#bbbbbb');
grandparent.append("text")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".75em");
var year = 2016;
var abs_in_year = function (year, values) {
val = values.filter(function (el) {
var key = parseInt(Object.keys(el));
return (key === year);
})[0][year]['abs'];
return val;
};
d3.json("bilancio.json", function(data) {
var root = d3.hierarchy(data);
console.log(root);
treemap(root
.sum(function (d) {
if (!d.hasOwnProperty('children'))
return abs_in_year(year, d.values);
else
return 0.0;
})
.sort(function (a, b) {
return b.height - a.height || b.value - a.value
})
);
display(root);
function display(d) {
// write text into grandparent
// and activate click's handler
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(breadcrumbs(d));
// grandparent color
grandparent
.datum(d.parent)
.select("rect")
.attr("fill", function () {
return '#bbbbbb'
});
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d.children)
.enter().
append("g");
// add class and click handler to all g's with children
g.filter(function (d) {
return d.children;
})
.attr("class", "children")
.style("cursor", "pointer")
.on("click", transition);
g.selectAll(".child")
.data(function (d) {
return d.children || [d];
})
.enter().append("rect")
.attr("class", "child")
.call(rect);
// add title to parents
g.append("rect")
.attr("class", "parent")
.call(rect)
.append("title")
.text(function (d){
return name(d);
});
/* Adding a foreign object instead of a text object, allows for text wrapping */
g.append("foreignObject")
.call(rect)
.attr("class", "foreignobj")
.append("xhtml:div")
.attr("title", function(d) {
return name(d);
})
.html(function (d) {
return '' +
'<p class="title">' + name(d) + '</p>' +
'<p>' + formatNumber(d.value) + '</p>'
;
})
.attr("class", "textdiv"); //textdiv class allows us to style the text easily with CSS
function transition(d) {
if (transitioning || !d) return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(650),
t2 = g2.transition().duration(650);
// Update the domain only after entering new elements.
x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function (a, b) {
return a.depth - b.depth;
});
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
g2.selectAll("foreignObject div").style("display", "none");
/*added*/
// Transition to the new view.
t1.selectAll("text").call(text).style("fill-opacity", 0);
t2.selectAll("text").call(text).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
/* Foreign object */
t1.selectAll(".textdiv").style("display", "none");
/* added */
t1.selectAll(".foreignobj").call(foreign);
/* added */
t2.selectAll(".textdiv").style("display", "block");
/* added */
t2.selectAll(".foreignobj").call(foreign);
/* added */
// Remove the old node when the transition is finished.
t1.on("end.remove", function(){
this.remove();
transitioning = false;
});
}
return g;
}
function text(text) {
text.attr("x", function (d) {
return x(d.x) + 6;
})
.attr("y", function (d) {
return y(d.y) + 6;
});
}
function rect(rect) {
rect
.attr("x", function (d) {
return x(d.x0);
})
.attr("y", function (d) {
return y(d.y0);
})
.attr("width", function (d) {
return x(d.x1) - x(d.x0);
})
.attr("height", function (d) {
return y(d.y1) - y(d.y0);
})
.attr("fill", function (d) {
return '#bbbbbb';
});
}
function foreign(foreign) { /* added */
foreign
.attr("x", function (d) {
return x(d.x0);
})
.attr("y", function (d) {
return y(d.y0);
})
.attr("width", function (d) {
return x(d.x1) - x(d.x0);
})
.attr("height", function (d) {
return y(d.y1) - y(d.y0);
});
}
function name(d) {
return d.data.label;
}
function breadcrumbs(d) {
var res = "";
var sep = " > ";
d.ancestors().reverse().forEach(function(i){
res += name(i) + sep;
});
res = res
.split(sep)
.filter(function(i){
return i!== "";
})
.join(sep);
return res +
(d.parent
? " - Click to zoom out"
: " - Click inside square to zoom in");
}
});
</script>
https://d3js.org/d3.v4.min.js