This visualization was created using data from the Bureau of Investigative Journalism. To see the full visualization, please click "open" on the right. To learn more about the United States drone program, head to their website here: https://www.thebureauinvestigates.com/projects/drone-war
xxxxxxxxxx
<html>
<head>
<title>Drones in Yemen</title>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.0/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.0/d3-legend.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div id="title">US Strikes in Yemen</div>
<div class="info"><i>*Scroll or double-click to zoom.<br>Drag slider to change date.*</i></div>
<input type="range" min="1036000000000" max="1510462800000" class="slider" value="0" id ="slider"/>
</body>
<script>
var width = 700
var height = 600
var scale = width * 5
var q = d3.queue();
q.defer(d3.request, "yemen.json")
q.defer(d3.request, "droneData.csv")
q.await(function(error, yemen, droneData) {
if (error) throw error;
var drones = d3.csvParse(droneData.responseText)
var yemen = JSON.parse(yemen.responseText)
var tooltip = d3.select("body").append("div")
.attr("id", "tooltip")
.classed("showing", true)
.style("opacity", 0)
.classed("showing", true)
.style("z-index", 3)
var zoomExtent = d3.zoom().scaleExtent([1, 20]);
var svg = d3.select("body").append("svg")
.classed("svg", true)
.attr("width", width)
.attr("height", height)
.call(zoomExtent
.on("zoom", zoom))
var g = d3.select("svg")
.append("g")
.attr("id", "mainChart")
function zoom() {
d3.select("rect#dataBackground").remove();
let k = d3.event.transform.k
g.attr("transform", d3.event.transform)
g.selectAll("circle")
.transition()
.attr("r", d => (1 + Math.sqrt((d.minKilled) / Math.PI) * 4) / k)
.style("fill", d => d.minChildren ? "red" : "orange")
}
var svg2 = d3.select("body").append("svg")
.classed("svg2", true)
.attr("width", width)
.attr("height", height / 2 + 5)
svg2.append("g")
.attr("id", "barGroup")
.attr("transform", "translate(50,0)")
// DRAW OUR Districts //
var projection = d3.geoMercator()
.scale(scale)
.translate([-2550, 1325])
var geoPath = d3.geoPath()
.projection(projection);
g.selectAll("path")
.data(yemen.features) // Must access the features array
.enter()
.append("path")
.attr("d", geoPath)
.attr("class", "districts")
.attr("id", function(d, i) {
if (d.properties.name) {
return d.properties.name.replace(/\s+/g, '')
}
})
.attr("provKilled", 0)
.attr("provStrikes", 0)
.on("mouseover", showData)
.on("mousemove", moveData)
.on("mouseleave", hideData)
.classed("notYemen", function(d) {
return d.properties.sov_a3 == "YEM" ? false : true
})
// Add Labels
var allProvinces = d3.selectAll("path").data()
var provinceNames = []
allProvinces.forEach(function(d) {
// Get province name
let provName = d.properties.name
// Find center of Province
let thisCenter = geoPath.centroid(d);
thisCenter.push(provName)
provinceNames.push(thisCenter)
})
g.selectAll("text")
.data(provinceNames)
.enter()
.append("text")
.attr("x", (d, i) => d[0])
.attr("y", (d, i) => d[1])
.text(d => (d[2]))
.style("text-anchor", "middle")
// Process Drone data
var totalKilled = 0;
var totalStrikes = 0;
var mappableStrikes = []
drones.forEach(function(d) {
d.UTC = Date.parse(d.Date)
d.Drone = +d.Drone
d.US = +d.US
d.Province = d.Province.replace(/\s+/g, '')
d.minChildren = +d.minChildren
d.minCivilians = +d.minCivilians
d.minInjured = +d.minInjured
d.minKilled = +d.minKilled
d.minStrikes = +d.minStrike
d.Lat = +d.Lat
d.Lon = +d.Lon
totalKilled = totalKilled + d.minKilled
totalStrikes++
if ((d.Drone) && (d.US)) {
let provinceTarget = `#${d.Province}`
if (provinceTarget != "#Unknown") {
var districtPath = d3.select(provinceTarget)
var currentKilled = districtPath.attr("provKilled")
var currentStrikes = d3.select(provinceTarget).attr("provStrikes")
var updateKilled = parseInt(currentKilled) + parseInt(d.minKilled)
var updateStrikes = parseInt(currentStrikes) + 1
districtPath.attr("provKilled", updateKilled)
districtPath.attr("provStrikes", updateStrikes)
}
}
if ((d.Lat) && (d.Lon)) {
mappableStrikes.push(d)
}
})
// Slider
d3.select("input")
.on("mousemove", remapData)
d3.select("body")
.append("div")
.classed("legendData", true)
.attr("x", (width / 2) / 2)
.attr("y", 50)
.attr("id", "barData")
.html("By 11/2/2002, the United States military had launched <span class='warning'>0 strikes</span> in Yemen, killing a total of <span class 'warning'>0 people</span>.")
// Add Scale + Initialize Force
var timeParse = d3.timeParse("%m/%d/%y")
var xScale = d3.scaleTime()
.range([0, 550]).domain([timeParse("01/01/02"), timeParse("01/01/18")])
var yScale = d3.scaleLinear()
.range([225, 0])
.domain([0, 55])
var xAxis = d3.axisBottom(xScale)
.ticks(15)
.tickSize(225)
var yAxis = d3.axisLeft(yScale)
.ticks(10)
.tickSize(560)
d3.select("#barGroup")
.append("g")
.attr("id", "xAxis")
.call(xAxis)
.attr("transform", "translate(25,25)")
d3.select("#barGroup")
.append("g")
.attr("id", "yAxis")
.attr("transform", "translate(575,25)")
.call(yAxis)
d3.selectAll("path.domain").attr("d", "M0.5,-1V0.5H550.5V-1")
.attr("transform", "translate(0,225)")
d3.select("g#yAxis path.domain").remove();
d3.select("g#yAxis g.tick").remove()
d3.select("g#yAxis g.tick text").style("text-anchor", "end")
d3.select("#barGroup")
.append("text")
.text("Year of Strike")
.attr("x", width / 2 - 50)
.attr("y", height / 2 - 10)
.style("font-size", 15 + "px")
d3.select("#barGroup")
.append("text")
.text("Yemenis Killed")
.attr("x", -130)
.attr("y", -10)
.style("font-size", 15 + "px")
.attr("transform", "rotate(-90)")
// Mapping Data Function
function remapData(d) {
//Geographic
var currentVal = parseInt(document.getElementsByClassName("slider")["0"].value)
let topDate = new Date(currentVal)
let month = topDate.getMonth() + 1
let date = topDate.getDate()
let year = topDate.getFullYear()
let barStrikes = drones.filter(function(d) {
return d.UTC <= currentVal ? true : false;
});
let filtStrikes = mappableStrikes.filter(function(d) {
return d.UTC <= currentVal ? true : false;
})
let thusKilled = d3.sum(barStrikes, function(d) {
return d.minKilled
})
let dataBind = g.selectAll("circle")
.data(filtStrikes)
dataBind
.enter()
.append("circle")
.attr("class", (d, i) => "cir" + i)
.on("mouseover", showStrike)
.on("mouseleave", hideData)
.on("click", function(d, i) {
let val = d3.select(this).data()["0"].Date
d3.select("#slider").valueOf().node().value = d.UTC;
svg2.selectAll("rect").filter(function(d) {
if (d.UTC > parseInt(document.getElementsByClassName("slider")["0"].value)) {
return true
} else {
return false
}
}).remove();
showStrike();
})
.attr("cx", d => projection([d.Lat, d.Lon])[0])
.attr("cy", d => projection([d.Lat, d.Lon])[1])
.transition()
.attr("r", d => 1 + Math.sqrt((d.minKilled) / Math.PI) * 4)
.style("fill", d => d.minChildren ? "red" : "orange")
dataBind
.exit()
.remove()
let dataBind2 = d3.select("#barGroup").selectAll("rect")
.data(filtStrikes)
dataBind2.enter()
.append("rect")
.attr("class", (d, i) => "cir" + i)
.on("mouseover", showStrike)
.on("mouseleave", hideData)
.on("click", function(d, i) {
let val = d3.select("#slider").valueOf().node().value
d3.select("#slider").valueOf().node().value = d.UTC
showStrike();
})
.attr("x", d => xScale(timeParse(d.Date)))
.attr("y", d => yScale(d.minKilled) + 24)
.attr("height", d => yScale(55 - d.minKilled))
.attr("width", 10)
.style("fill", d => d.minChildren ? "red" : "orange")
.attr("transform", "translate(20,0)")
dataBind2
.exit()
.remove()
d3.select("#barData").html(`By ${month}/${date}/${year}, the United States military had launched <span class='warning'>${barStrikes.length} strikes</span> in Yemen, killing a total of <span class='warning'>${thusKilled} people</span>.`)
}
// Color and Label Districts
var allDist = document.getElementsByClassName("districts")
var killRange = d3.extent(allDist, function(d, i) {
return parseInt(d.getAttribute("provKilled"))
})
var redScale = d3.scaleLinear().domain(killRange).range(["#FFECEC", "#BA0514"])
function labelDistricts() {
var hitRegions = d3.selectAll("path.districts").filter(function(d) {
return d3.select(this).attr("provStrikes") != 0
})
.style("fill", function() {
return redScale(parseInt(d3.select(this).attr("provKilled")))
})
var distNames = []
hitRegions.each(function(d) {
var distName = d.properties.districts
var thisCenter = geoPath.centroid(d)
thisCenter.push(distName)
distNames.push(thisCenter)
})
g.selectAll("text")
.data(distNames)
.enter()
.append("text")
.attr("x", d => d[0])
.attr("y", d => d[1])
.text(d => d[2])
}
labelDistricts();
// Add tooltip
function showData(d) {
let thisProv = d3.select(this)
tooltip.html("<b>Province:</b> " + thisProv.attr("id") + "<br><b>Strikes</b>: " + thisProv.attr("provStrikes") + "<br><b>Killed</b>: " + thisProv.attr("provKilled"))
var coordinates
coordinates = d3.event;
var x = coordinates.x
var y = coordinates.y
tooltip.style("left", x + 30 + "px")
tooltip.style("top", y + "px")
d3.select("#tooltip").transition().duration(250).style("opacity", 1)
}
function moveData(d) {
var coordinates
coordinates = d3.event;
var x = coordinates.x
var y = coordinates.y
tooltip.style("left", x + 30 + "px")
tooltip.style("top", y + "px")
}
function hideData(d) {
d3.select("#tooltip").transition().duration(250).style("opacity", 0)
if (this.hasAttribute("cx") || this.hasAttribute("x")) {
let cirNo = d3.select(this).attr("class")
d3.selectAll(`.${cirNo}`)
.style("fill", function(d) {
return d.minChildren ? "red" : "orange"
})
.style("opacity", .75)
}
}
function showStrike(d) {
if (event.type != "click") {
tooltip.html("<u><b>Strike</b></u><br>" + "<b>Location</b>: " + d.Location + "<br><b>Date:</b> " + d.Date + "<br><b>Minimum Killed</b>: " + d.minKilled + "<br><b>Minimum Injured</b>: " + d.minInjured + "<br><b>Minimum Children Killed</b>: " + d.minChildren + "<b><br>Type:</b> " + d.Type)
var coordinates
coordinates = d3.event;
var x = coordinates.x
var y = coordinates.y
tooltip.style("left", x + 30 + "px")
tooltip.style("top", y + "px")
// this.parentNode.appendChild(this)
cirNo = d3.select(this).attr("class")
d3.selectAll(`.${cirNo}`)
.style("fill", "#00FFFF")
.style("opacity", 1)
d3.select("#tooltip").transition().duration(250).style("opacity", 1)
} else {
d3.selectAll("circle").filter(function(d) {
if (d.UTC > parseInt(document.getElementsByClassName("slider")["0"].value)) {
return true
} else {
return false
}
}).attr("r", 0).remove();
svg2.selectAll("rect").filter(function(d) {
if (d.UTC > parseInt(document.getElementsByClassName("slider")["0"].value)) {
return true
} else {
return false
}
}).remove();
}
}
// LEGEND //
var linearSize = d3.scaleLinear().domain([0, 60]).range([2, 18.73656774376801]);
svg.append("g")
.attr("class", "legendSize")
.attr("transform", "translate(50, 175)");
var legendSize = d3.legendSize()
.scale(linearSize)
.shape('circle')
.shapePadding(15)
.labelOffset(10)
.orient('horizontal');
svg.select(".legendSize")
.call(legendSize);
d3.selectAll("g.cell text.label").text((d, i) => i * 15)
d3.select("g.cell text.label").text(1)
d3.selectAll("text.label").attr("transform", (d, i) => `translate(0,${i*5 + 15})`)
svg2.append("g").append("svg:image")
.attr("x",10)
.attr("y",10)
.attr("width",30)
.attr("height",30)
.attr("xlink:href","https://s3.amazonaws.com/aws-website-ngivisualization-md5xp/reseticon.png")
.on("mouseover",function(){ d3.select(this).style("opacity",.5)})
.on("mouseleave",function(){ d3.select(this).style("opacity",1)})
.on("click", function(){
d3.select("#slider").valueOf().node().value = 0
animate();
})
$(document).ready(animate);
function animate(){
var val;
var inte = setInterval(function() {
val = d3.select("#slider").valueOf().node().value
val = parseInt(val) + 5557600000;
d3.select("#slider")
.valueOf().node().value = val
remapData();
if (val > $('#slider').attr('max')) {
clearInterval(inte);
}
}, 50);
}
});
</script>
</html>
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js
https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.0/d3.min.js
https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.0/d3-legend.min.js