Skip to content

Instantly share code, notes, and snippets.

@sabrinadchan
Last active August 6, 2017 15:37
Show Gist options
  • Save sabrinadchan/f96f7379174fa8c244b05c0ae0156428 to your computer and use it in GitHub Desktop.
Save sabrinadchan/f96f7379174fa8c244b05c0ae0156428 to your computer and use it in GitHub Desktop.
Chicago West Side Homicide Density
license: gpl-3.0
height: 700

The above visualization uses d3-contour to estimate the density of gun homicides on Chicago's West Side. The animation shows how the choice of bandwidth impacts the resulting density contours when performing a kernel density estimation on a set of data. Click "pause" to stop the animation. Click "Step Forward" and "Step Backward" to increment and deincrement the bandwidth by 1.

Gun homicide data was gathered from the Gun Violence Archive. The data was cleansed and refined using numerous Python scripts as part of an ongoing personal project on Chicago Gun Violence. I do not claim that the data is accurate. Due to the nature of the data, homicides that occurred on the same city block are plotted in the same location, even if the incidents took place at different addresses. The data being considered are 2016 gun homicides that occurred on Chicago's West Side in the Austin, East Garfield Park, West Garfield Park, Humboldt Park, North Lawndale, South Lawndale, West Town, Near West Side, and Lowest West Side community areas.

<!DOCTYPE html>
<style>
body {
font: 16px sans-serif;
}
</style>
<body>
<div class="buttons">
<input name="resume" type=button value="Resume" onclick="resume()" />
<input name="pause" type=button value="Pause" onclick="pause()" />
<input name="ff" type=button value="Step Forward" onclick="stepForward()" />
<input name="rw" type=button value="Step Back" onclick="stepBack()" />
</div>
<p>Current bandwith: <span id="text-span"></span></p>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-contour.v1.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script type="text/javascript">
var margin = {top: 20, right: 20, bottom: 20, left: 20},
outerWidth = 960,
outerHeight = 600,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom;
var topo;
var bandwidth = 5;
var interval = 1000;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var bottomLayer = svg.append("g")
.attr("class", "bottom-layer");
var color = d3.scaleSequential(d3.interpolateYlGnBu)
.domain([0, 0.00375]);
var projection = d3.geoEquirectangular()
.rotate([88 + 20 / 60, -36 - 40 / 60]);
var path = d3.geoPath()
.projection(projection);
d3.queue()
.defer(d3.json, "west_side.topojson")
.defer(d3.json, "west_side_homicides.topojson")
.await(ready);
function ready(error, community, homicides) {
if (error) throw error;
// start with unit projection
projection
.scale(1)
.translate([0, 0]);
// then translate and scale according to topoJSON's bbox
var b = path.bounds(topojson.feature(community, community.objects.west_side)),
s = 0.95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
svg.append("clipPath")
.attr("id", "boundary")
.append("path")
.datum(topojson.feature(community, community.objects.west_side))
.attr("d", path);
svg.append("path")
.datum(topojson.feature(community, community.objects.west_side))
.attr("d", path)
.attr("stroke", "black")
.attr("fill", "none")
.attr("stroke-width", 2);
topo = topojson.feature(homicides, homicides.objects.west_side_homicides).features;
update(bandwidth);
}
function update(bandwidth) {
svg.selectAll(".contours")
.remove();
svg.append("g", "g")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.selectAll("path")
.data(d3.contourDensity()
.x(d => projection(d.geometry.coordinates)[0])
.y(d => projection(d.geometry.coordinates)[1])
.size([width, height])
.bandwidth(bandwidth)
(topo))
.enter().append("path")
.attr("class", "contours")
.attr("clip-path", "url(#boundary)")
.attr("fill", d => color(d.value))
.attr("opacity", 0.25)
.attr("d", d3.geoPath());
d3.select("#text-span")
.html(bandwidth);
}
var currId;
function Animation(callback, interval) {
var animationId, active;
this.pause = () => {
active = false;
clearInterval(animationId);
}
this.resume = () => {
active = true;
animationId = setInterval(callback, interval);
}
this.active = () => active;
this.resume();
}
function pause() {
if (timer.active()) {
timer.pause();
}
}
function resume() {
if (!timer.active()) {
timer.resume();
}
}
function stepForward() {
pause();
if (bandwidth < 50) {
bandwidth += 1;
}
update(bandwidth);
}
function stepBack() {
pause();
if (bandwidth > 5) {
bandwidth -= 1;
}
update(bandwidth);
}
var timer = new Animation(function () {
if (bandwidth == 50) {
bandwidth = 5;
} else {
bandwidth += 1;
}
update(bandwidth);
}, interval);
</script>
</body>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment