Skip to content

Instantly share code, notes, and snippets.

@nacmonad
Last active January 8, 2016 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nacmonad/77a772c5fb7f793ef3d4 to your computer and use it in GitHub Desktop.
Save nacmonad/77a772c5fb7f793ef3d4 to your computer and use it in GitHub Desktop.
d3longitudinalchoropleth

A longitudinal choropleth implemented in d3 -- this one features total homocides per province and territory. It is not per capita homicides.

Please view in new window as there are html elements outside of the existing svg.

TODO: Trying to use CSS iframe { overflow:scroll !important } to enable vertical scrolling within the block viewer frame but not happening yet, any suggestions??

It can be improved by using an update() method for the data, rather than explicitly declaring vars for storing data for each year and using switch statements to cycle between.

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.
NAME CODE thirteen twelve eleven ten nine eight seven
Newfoundland and Labrador CA05 7 3 4 4 1 5 3
Prince Edward Island CA09 1 0 1 0 0 2 0
Nova Scotia CA07 13 17 22 21 15 12 13
New Brunswick CA04 7 6 8 9 12 3 8
Quebec CA10 68 108 105 84 88 92 90
Ontario CA08 166 162 161 189 178 176 202
Manitoba CA03 49 52 53 45 57 54 61
Saskatchewan CA11 30 29 38 34 36 30 30
Alberta CA01 82 85 109 77 95 110 88
British Columbia CA02 76 71 87 83 118 117 88
Yukon CA12 0 0 0 1 2 3 2
Northwest Territories CA06 2 5 3 1 2 3 2
Nunavut CA13 4 5 7 6 6 4 7
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canada Homicides Longitudinal Choropleth </title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
<style>
svg {
border-radius: 25px;
}
#myMap {
margin-left: auto;
margin-right: auto;
width:750px;
height:500px;
}
#yearSlider {
display: block;
text-align: center;
margin-top:25px;
margin-left: auto;
margin-right: auto;
width:750px;
}
.background {
fill: #eee;
pointer-events: all;
}
.regions {
cursor: pointer;
}
.regions.active {
}
.tooltip {
background-color: #fff;
border: 1px solid #555;
border-radius: 8px;
padding: 5px;
}
path {
stroke: #dddddd;
stroke-width: 0.8;
}
path:hover, path.highlighted {
fill-opacity: 0.8;
}
iframe{ overflow-x: hidden; overflow-y: scroll !important} /*trying to mess with the bl.ocks viewer to add vertical scrolling */
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<div class = "container">
<div class= "text-center">
<h3>Canadian Total Homocides per Province/Territory</h3>
<h4><a href="http://bl.ocks.org/nacmonad/raw/77a772c5fb7f793ef3d4/" target = "_parent">Please enjoy in new window</a></h4>
</br><hr> <!-- note br & hr inside of container now -->
</div>
<div id= "myMap"></div>
<div class = "container text-center" >
<h5>Canadian Homicides</h5>
<h6>Year: <span class = "sliderText">2007</span></h6>
<input type="range" min="2007" max="2013" step="1" value="2007" id="yearSlider">
</div>
</div>
<script>
// Setting color domains(intervals of values) for our map
var color_domain = [1, 5, 10, 20, 30,50,75,100,150,200]
var ext_color_domain = [0, 1, 5, 10, 20, 30,50,75,100,150,200]
var legend_labels = ["0", "1-5", "6-10", "11-20", "21-30", "31-50","51-75","76-100","101-150","150-200",">200"]
var color = d3.scale.linear()
.domain(color_domain)
// .range(["#f7fcfd", "#e5f5f9", "#ccece6", "#99d8c9", "#66c2a4", "#41ae76","#238b45","#006d2c","#00441b","#003008"]); greens
//.range(["rgb(247,251,255)", "rgb(222,235,247)", "rgb(198,219,239)", "rgb(158,202,225)", "rgb(107,174,214)", "rgb(66,146,198)","rgb(33,113,181)","rgb(8,81,156)","rgb(8,48,107)","rgb(4,18,99)"]); blues
.range (["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d","#55000a"]); //reds
var width = 750,
height = 500,
active = d3.select(null);
var projection = d3.geo.albers()
.scale(529.7143908533221)
.center([-96.64103465647952,60.538257788841584]) //projection center
.parallels([41.9714969544088,83.1480859363606]) //parallels for conic projection
.rotate([96.64103465647952]) //rotation for conic projection
.translate([-97.20316630291671+width/4,40.16223755810421]) //translate to center the map in view
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, 8])
.on("zoom", zoomed);
var svg = d3.select("#myMap").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom).on("dblclick.zoom", null);
// container
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var path = d3.geo.path()
.projection(projection);
var thirteen = d3.map();
var twelve = d3.map();
var eleven = d3.map();
var ten = d3.map();
var nine = d3.map();
var eight = d3.map();
var seven = d3.map();
var tooltip = d3.select("body") // change to "rect"
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.attr("class", "tooltip")
.text("thetooltip");
//Setup Slider and slider events
var sliderValue = 2007;
var slider = d3.select('input')
.on("input", function() {
d3.selectAll('.sliderText').text(this.value);
var sliderValue = this.value;
console.log(sliderValue);
//update fill attributes for paths on slider events
d3.select('.regions')
.selectAll('path')
.attr("fill", function(d){
switch(parseInt(sliderValue)) {
case 2007:
console.log(d.properties.CODE);
return color(seven.get(d.properties.CODE));
break;
case 2008:
console.log(d.properties.CODE);
return color(eight.get(d.properties.CODE));
break;
case 2009:
console.log(d.properties.CODE);
return color(nine.get(d.properties.CODE));
break;
case 2010:
console.log(d.properties.CODE);
return color(ten.get(d.properties.CODE));
break;
case 2011:
console.log(d.properties.CODE);
return color(eleven.get(d.properties.CODE));
break;
case 2012:
console.log(d.properties.CODE);
return color(twelve.get(d.properties.CODE));
break;
case 2013:
console.log(d.properties.CODE);
return color(thirteen.get(d.properties.CODE));
break;
}
});
});
//setup queue
queue()
.defer(d3.json, "./canada.topojson")
.defer(d3.csv, "./homocide.csv", function(d) {
thirteen.set(d.CODE, +d.thirteen);
twelve.set(d.CODE, +d.twelve);
eleven.set(d.CODE, +d.eleven);
ten.set(d.CODE, +d.ten);
nine.set(d.CODE, +d.nine);
eight.set(d.CODE, +d.eight);
seven.set(d.CODE, +d.seven);
})
.await(ready);
//drawing routine inside ready
function ready(error, map) {
if (error) throw error;
//draw map
var g = svg.append("g")
.attr("class","regions")
.selectAll("path")
.data(topojson.feature(map, map.objects.collection).features)
.enter().append("path")
.attr("d", path)
.attr("fill", function(d){
switch(sliderValue) {
//only need this case because this occurs on the initial browser render()
case 2007:
return color(seven.get(d.properties.CODE));
break;
}
})
.on("click", clicked)
.on("mouseover", function(d) {
tooltip.style("visibility", "visible");
switch(sliderValue){
case 2007:
tooltip.html("Name: " + d.properties.NAME + "<br>" + "Year: " + sliderValue + "<br>" + "Homocides: " + seven.get(d.properties.CODE));
break;
case 2008:
tooltip.html("Name: " + d.properties.NAME + "<br>" + "Year: " + sliderValue + "<br>" + "Homocides: " + eight.get(d.properties.CODE));
break;
case 2009:
tooltip.html("Name: " + d.properties.NAME + "<br>" + "Year: " + sliderValue + "<br>" + "Homocides: " + nine.get(d.properties.CODE));
break;
case 2010:
tooltip.html("Name: " + d.properties.NAME + "<br>" + "Year: " + sliderValue + "<br>" + "Homocides: " + ten.get(d.properties.CODE));
break;
case 2011:
tooltip.html("Name: " + d.properties.NAME + "<br>" + "Year: " + sliderValue + "<br>" + "Homocides: " + eleven.get(d.properties.CODE));
break;
case 2012:
tooltip.html("Name: " + d.properties.NAME + "<br>" + "Year: " + sliderValue + "<br>" + "Homocides: " + twelve.get(d.properties.CODE));
break;
case 2013:
tooltip.html("Name: " + d.properties.NAME + "<br>" + "Year: " + sliderValue + "<br>" + "Homocides: " + thirteen.get(d.properties.CODE));
break;
}
})
.on("mousemove", function() {
return tooltip
.style("top", (d3.event.pageY -10 + "px"))
.style("left", (d3.event.pageX +10 + "px"));
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
//add legend
//Adding legend for our Choropleth
var legend = svg.selectAll("g.legend")
.data(ext_color_domain)
.enter().append("g")
.attr("class", "legend");
var ls_w = 20, ls_h = 20;
legend.append("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 1);
legend.append("text")
.attr("x", 50)
.attr("y", function(d, i){ return height - (i*ls_h) - ls_h - 4;})
.text(function(d, i){ return legend_labels[i]; });
}
function clicked(d) {
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
svg.transition()
.duration(750)
.call(zoom.translate(translate).scale(scale).event);
}
function zoomed() {
svg.selectAll(".regions").style("stroke-width", 1.5 / d3.event.scale + "px");
svg.selectAll(".regions").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
svg.transition()
.duration(750)
.call(zoom.translate([0, 0]).scale(1).event);
}
// If the drag behavior prevents the default click,
// also stop propagation so we don’t click-to-zoom.
function stopped() {
if (d3.event.defaultPrevented) d3.event.stopPropagation();
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment