|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>D3 Example</title> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.js"></script> |
|
<script src="http://d3js.org/colorbrewer.v1.min.js"></script> |
|
<link href='http://fonts.googleapis.com/css?family=Lato' rel='stylesheet' type='text/css'> |
|
<style> |
|
path { |
|
stroke: gray; |
|
stroke-width: 0.1px; |
|
} |
|
text { |
|
font-family: 'Lato', sans-serif; |
|
font-size: 1.5em; |
|
|
|
/* This trick adds a heavy white shadow around the text. */ |
|
text-shadow: |
|
0px 0px 6px white, |
|
0px 0px 6px white, |
|
0px 0px 6px white, |
|
0px 0px 6px white, |
|
0px 0px 6px white, |
|
0px 0px 6px white, |
|
0px 0px 6px white, |
|
0px 0px 6px white, |
|
0px 0px 6px white; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<script> |
|
|
|
var width = 960; |
|
var height = 500; |
|
|
|
var csvFile = "Unemployment_Percentage_by_Census_Tract_San_Mateo_County_2010.csv"; |
|
var topoJSONFile = "tl_2010_06081_tract10.json"; |
|
|
|
// Use mercator so the map matches people's expectation, |
|
// which has been learned from using Google Maps. |
|
var projection = d3.geo.mercator() |
|
.scale(65000) |
|
.translate([width / 2, height / 2]) |
|
.center([-122.4, 37.54]); |
|
|
|
var path = d3.geo.path() |
|
.projection(projection); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
var choroplethG = svg.append("g"); |
|
|
|
var legendFormat = d3.format(".1%"); |
|
var legendSpacing = 40; |
|
var legendLabelOffset = 35; |
|
var legendBoxSize = 30; |
|
var legendG = svg.append("g") |
|
.attr("transform", "translate(50, 50)"); |
|
|
|
var tooltip = svg.append("text").attr("class", "tooltip"); |
|
var tooltipXOffset = 10; |
|
|
|
var color = d3.scale.quantile() |
|
.range(colorbrewer.Greys[9]); |
|
|
|
var zoom = d3.behavior.zoom() |
|
.scaleExtent([1, 20]) |
|
.on("zoom", function (){ |
|
choroplethG.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")"); |
|
}); |
|
svg.call(zoom); |
|
|
|
function showTooltip(data){ |
|
return function(d){ |
|
var value = data.get(d.properties.TRACTCE10); |
|
tooltip |
|
.attr("x", d3.event.pageX + tooltipXOffset) |
|
.attr("y", d3.event.pageY) |
|
.text(legendFormat(value) + " unemployment"); |
|
}; |
|
} |
|
function hideTooltip(){ |
|
tooltip.text(""); |
|
} |
|
|
|
fetchData(function (data){ |
|
fetchFeatures(function (features){ |
|
render(features, data); |
|
}); |
|
}); |
|
|
|
function fetchData(callback){ |
|
|
|
// "data" will be a hash where keys are census tract codes |
|
// and values are unemployment values. |
|
var data = d3.map(); |
|
|
|
d3.csv(csvFile, function (d){ |
|
var id = d["Tract ID"]; |
|
var unemployment = parseFloat(d["Unemployment Rate"].replace("%","")) / 100; |
|
data.set(id, unemployment); |
|
}, function(){ callback(data); }); |
|
} |
|
|
|
function fetchFeatures(callback){ |
|
d3.json(topoJSONFile, function(err, tracts){ |
|
|
|
// Parse the topoJSON structure. |
|
var features = topojson.feature(tracts, tracts.objects.tl_2010_06081_tract10).features; |
|
|
|
// Exclude the census tract in the water that has no unemployment data. |
|
callback(features.filter(function (d){ |
|
return d.properties.TRACTCE10 !== "990100"; |
|
})); |
|
}); |
|
} |
|
|
|
function render(features, data){ |
|
color.domain(data.values()); |
|
renderChoropleth(features, data); |
|
renderLegend(data); |
|
} |
|
|
|
function renderChoropleth(features, data){ |
|
choroplethG.selectAll("path") |
|
.data(features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("fill", function (d){ |
|
return color(data.get(d.properties.TRACTCE10)); |
|
}) |
|
.on("mouseover", showTooltip(data)) |
|
.on("mousemove", showTooltip(data)) |
|
.on("mouseout", hideTooltip); |
|
} |
|
|
|
function renderLegend(data){ |
|
var extent = d3.extent(data.values()); |
|
var legendPoints = [extent[0]].concat(color.quantiles(), extent[1]); |
|
var legendEntries = []; |
|
legendPoints.forEach(function(d, i){ |
|
if(i < legendPoints.length - 1){ |
|
legendEntries.push([ |
|
legendPoints[i], |
|
legendPoints[i + 1] |
|
]); |
|
} |
|
}); |
|
|
|
var legend = legendG.selectAll("g.legendEntry") |
|
.data(legendEntries) |
|
.enter() |
|
.append("g").attr("class", "legendEntry"); |
|
|
|
legend |
|
.append("rect") |
|
.attr("x", 0) |
|
.attr("y", function(d, i) { return i * legendSpacing; }) |
|
.attr("width", legendBoxSize) |
|
.attr("height", legendBoxSize) |
|
.style("stroke", "gray") |
|
.style("stroke-width", "1px") |
|
.attr("fill", function(d){ |
|
return color(d[0]); |
|
}); |
|
|
|
legend |
|
.append("text") |
|
.attr("x", legendLabelOffset) |
|
.attr("y", function(d, i) { return i * legendSpacing; }) |
|
.attr("dy", "1em") |
|
.text(function(d) { |
|
var min = legendFormat(d[0]); |
|
var max = legendFormat(d[1]); |
|
return min + " - " + max; |
|
}); |
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |