This visualization was featured in my story about facial recognition databases for the Century Foundation. The story is online here: https://tcf.org/content/commentary/the-face-of-surveillance/
xxxxxxxxxx
<html>
<head>
<title>NGI Databases</title>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
</head>
<body>
<div id="viz" style="display: block; margin: 0 auto; width: 460px; font-family: Miller, Open Sans"></div>
<div id="toolTip" style="position: fixed; display: block; background-color: white; border: 2px solid white; padding: 10px; font-family: 'Open Sans'; opacity: 1; z-index: 10; font-size: 12px; box-shadow: 5px 5px 5px lightblue">
<div id="state" style="display: block; margin: 0 auto;"><u>Location:</u> Kentucky DMV</div>
<div id="total" style="display: block; margin: 0 auto;"><u>Number of Photos:</u> 18,400,000</div>
<div id="percent" style="display: block; margin: 0 auto;">or about 4% of NGI.</div>
</div>
</body>
<script>
$.ajax({
dataType: "json",
url: "facialData.json",
success: function(data){
var facialData = data.facialData
var newData = []
facialData.forEach(function(d){
if (d.faces > 1){
newData.push(d)}
})
newData.sort(function(a,b){
return a.faces - b.faces;
})
d3.select("div#viz").append("svg")
.attr("id","svg")
.attr("width", 460)
.attr("height", 460)
.style("background-color","white")
var nested = d3.nest()
.key(d => d.state)
.entries(newData)
var allFaces = {id: "All Faces", values: nested}
var root = d3.hierarchy(allFaces, d => d.values)
.sum(d => d.faces)
var treemapLayout = d3.treemap()
.size([440,440])
treemapLayout(root)
d3.select("svg").selectAll("rect").data(root.descendants())
.enter()
.append("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0 + 40)
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
.style("fill", function(d){
if (d.data.state){ return "#E7503F"}
else { return "grey"}
})
.style("stroke", "white")
.style("stroke-width", 2)
.on("mouseover", showData)
.on("mousemove", toolMove)
.on("mouseleave", hideData)
var counter = 0
function pulsatingAnimation() {
d3.selectAll("rect").filter(function(d){
return d.data.source === "Kentucky"})
.transition()
.duration(300)
.attr('opacity', 0.5)
.transition()
.duration(300)
.attr('opacity', 1)
.on('end', function(d){
if(counter == 0){
pulsatingAnimation();
}
});
}
pulsatingAnimation();
d3.select("svg").select("rect").remove()
d3.select("svg").select("rect").remove()
d3.select("svg").select("rect").remove()
function addCommas(nStr) {
nStr += '';
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
}
// Styling the box on hover
function showData(d) {
counter = 1;
d3.selectAll("rect").filter(function(d){
return d.data.source === "Kentucky"})
.attr("opacity", 1);
d3.select(this)
.transition()
.style("opacity",.5)
d3.select("#toolTip").transition().style("opacity",1)
var faceParsed = addCommas(d.data.faces)
d3.select("div#state").text("Location: " + d.data.source)
d3.select("div#total").text("Number of Photos: " + faceParsed)
d3.select("div#percent").text("About " +
Math.ceil((d.data.faces/411000000).toFixed(4)*100) + "% of NGI"
)
}
function toolMove(d){
var eX = d3.event.x
var eY = d3.event.y
var e = d3.event
if (eX > 225){
var x = e.x - 220
d.data.state ? y = e.y : y = e.y - 70
}
else {
var x = e.x + 20
d.data.state ? y = e.y : y = e.y - 70
}
var toolTip = document.getElementById("toolTip")
toolTip.style.left = x + "px"
toolTip.style.top = y + "px"
toolTip.style.display = "block"
toolTip.style.backgroundColor = "white"
toolTip.style.padding = 10 + "px"
toolTip.style.fontFamily = "Open Sans"
}
function hideData(d){
d3.select(this)
.transition()
.style("opacity",1)
}
// Chart Labels + tooltip
d3.select("svg").append("text").text("State Databases")
.style("fill","#E7503F")
.style("font-family","Open Sans")
.attr("transform","rotate(90)")
.attr("x", 130)
.attr("y", -445)
d3.select("svg").append("text").text("Federal Databases")
.style("fill","grey")
.style("font-family","Open Sans")
.attr("transform","rotate(90)")
.attr("x", 320)
.attr("y", -445)
var fullLabel =
d3.select("svg").append("text").text("The NGI Database System")
.attr("x", (250-40)/2)
.attr("y", 25)
.style("font-size","25px")
.style("text-anchor","center")
var left = document.getElementById("svg").getBoundingClientRect().left;
var top = document.getElementById("svg").getBoundingClientRect().top;
d3.select("#toolTip").style("left", left + 20 + "px").style("top", top + 50 + "px")
}
})
window.addEventListener("resize", function(){
var left = document.getElementById("svg").getBoundingClientRect().left;
var top = document.getElementById("svg").getBoundingClientRect().top;
d3.select("#toolTip").style("left", left + 20 + "px").style("top", top + 50 + "px")
})
</script>
</html>
https://d3js.org/d3.v4.min.js
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js