|
/* CENSUS API NOTES |
|
baseUrl: https://api.census.gov.data/ |
|
format: baseUrl/yearCollected/datasetName?get=NAME,tableName&for=geography*&key=apikey |
|
*/ |
|
|
|
$(document).ready(function() { |
|
var CONSTANTS = { |
|
API_KEY: "6d83e90a07eca747afdf37e4c9e3c4e9fe4bd341", |
|
BASE_URL: "https://api.census.gov/data/", |
|
TABLE_KEY: [ |
|
{ |
|
"tableID": "B02001_002E", |
|
"tableTitle": "Percent white" |
|
}, |
|
{ |
|
"tableID": "B02001_003E", |
|
"tableTitle": "Percent black" |
|
}, |
|
{ |
|
"tableID": "B02001_005E", |
|
"tableTitle": "Percent Asian" |
|
}, |
|
{ |
|
"tableID": "B03001_003E", |
|
"tableTitle": "Percent Hispanic" |
|
}, |
|
{ |
|
"tableID": "B06008_004E", |
|
"tableTitle": "Percent divorced" |
|
}, |
|
{ |
|
"tableID": "B06008_025E", |
|
"tableTitle": "Percent foreign born" |
|
}, |
|
{ |
|
"tableID": "B06012_002E", |
|
"tableTitle": "Percent below 100% of poverty level" |
|
}, |
|
{ |
|
"tableID": "B06009_002E", |
|
"tableTitle": "Percent with less than a H.S. diploma" |
|
}, |
|
{ |
|
"tableID": "B09001_001E", |
|
"tableTitle": "Percent under 18" |
|
} |
|
], |
|
COUNTIES: null, |
|
BUBBLES: null |
|
}; |
|
|
|
var demoLookup; |
|
var countyNames; |
|
var isHoverable = false; |
|
|
|
var App = { |
|
init: function() { |
|
//App.requestCensusData(); |
|
App.drawCountyMap(); |
|
App.appendDropDown(); |
|
App.bindEvents(); |
|
}, |
|
bindEvents: function() { |
|
//Change map on dropdown |
|
$(".demo-dropdown").change(function() { |
|
var $dropDownOption = $(".demo-dropdown").val(); |
|
var chosenDemoData = _.findWhere(CONSTANTS.TABLE_KEY, {tableTitle: $dropDownOption}) |
|
isHoverable = true; |
|
|
|
App.requestCensusData(chosenDemoData.tableID) |
|
$('.toggle-container').css("opacity", 1); |
|
}); |
|
//Switch to bubble map |
|
$('.toggle-container').on('click', '.switch', function(e) { |
|
e.stopPropagation(); |
|
e.preventDefault(); |
|
if ($('.switch').hasClass('noBubble')) { |
|
$('.switch').removeClass('noBubble'); |
|
$('.switch').addClass('bubble'); |
|
App.bubbleMap(); |
|
|
|
App.uncolorMap(); |
|
//CONSTANTS.COUNTIES |
|
//.style("fill", "#f2f2f2"); |
|
} else { |
|
$('.switch').removeClass('bubble'); |
|
$('.switch').addClass('noBubble'); |
|
CONSTANTS.BUBBLES |
|
.style("display", "none"); |
|
|
|
App.colorMap(demoLookup); |
|
} |
|
}); |
|
}, |
|
appendDropDown: function() { |
|
var dropDown = d3.select(".demo-dropdown") |
|
var tableTitleNames = _.pluck(CONSTANTS.TABLE_KEY, 'tableTitle'); |
|
|
|
//Append the disabled prompt text |
|
tableTitleNames.unshift("Pick a demographic"); |
|
|
|
dropDown.selectAll("option") |
|
.data(tableTitleNames) |
|
.enter() |
|
.append("option") |
|
.text(function(d) { return d; }) |
|
.attr("value", function(d) { return d; }) |
|
}, |
|
requestCensusData: function(tableDemo) { |
|
var yearCollected = 2015; |
|
var datasetName = "acs5"; |
|
var tableTotalPop = "B02001_001E"; //Total unweighted pop |
|
var tableDemo = tableDemo; //Dropdown demographic |
|
var geography = "county:*"; |
|
var query_url_total = CONSTANTS.BASE_URL + yearCollected + "/" + datasetName + "?get=NAME," + tableTotalPop + "&for=" + geography + "&key=" + CONSTANTS.API_KEY; |
|
var query_url_demo = CONSTANTS.BASE_URL + yearCollected + "/" + datasetName + "?get=NAME," + tableDemo + "&for=" + geography + "&key=" + CONSTANTS.API_KEY; |
|
|
|
demoLookup = {}; |
|
|
|
return ( |
|
//Make the initial call for the total population |
|
$.ajax(query_url_total, { |
|
dataType: "json", |
|
success: function(response) { |
|
response.forEach(function(item, i) { |
|
// if (i) { |
|
var countyFips = item[2] + item[3]; |
|
var countyStateArray = item[0].split(', ') |
|
demoLookup[countyFips] = { |
|
totalPop: item[1], |
|
stateName: countyStateArray[1], |
|
countyName: countyStateArray[0] |
|
} |
|
|
|
// } |
|
}) |
|
|
|
//Make the call for the other variable |
|
$.ajax(query_url_demo, { |
|
dataType: "json", |
|
success: function(response) { |
|
response.forEach(function(item, i) { |
|
if(i) { |
|
var countyFips = item[2] + item[3]; |
|
demoLookup[countyFips]["demo"] = item[1]; |
|
demoLookup[countyFips]["percentage"] = (demoLookup[countyFips]["demo"]/demoLookup[countyFips]["totalPop"]); |
|
} |
|
}) |
|
if ($('.switch').hasClass('noBubble')) { |
|
App.colorMap(demoLookup); |
|
} else { |
|
App.bubbleMap(demoLookup); |
|
} |
|
}, |
|
error: function(error) { |
|
console.log(error); |
|
} |
|
}) |
|
}, |
|
error: function(error) { |
|
console.log(error); |
|
} |
|
}) |
|
); |
|
}, |
|
drawCountyMap: function() { |
|
//Margin conventions |
|
var margin = {top: 0, left: 0, bottom: 0, right: 0}, |
|
divWidth = d3.select(".map-container").node().clientWidth, |
|
width = divWidth - margin.left - margin.right, |
|
mapRatio = 0.6, |
|
height = width * mapRatio; |
|
|
|
//Map projection and dimensions |
|
var projection = d3.geo.albersUsa() |
|
.scale(width * 1.325) |
|
.translate([width/2, height/2]); |
|
|
|
var path = d3.geo.path() |
|
.projection(projection); |
|
|
|
//Attach tooltips |
|
var tooltip = d3.select("body").append("div") |
|
.attr("class", "tooltip") |
|
.style("opacity", 0); |
|
|
|
//Color scale |
|
var color = d3.scale.linear().domain([0,100]) |
|
.range(['#ffffd4, #993404']); |
|
|
|
//Append svg |
|
var svgMap = d3.select(".map-container").append("svg") |
|
.attr("width", width + 'px') |
|
.attr("height", height + 'px'); |
|
|
|
//Load in data |
|
queue() |
|
.defer(d3.json, "us2.json") |
|
.await(appendMap); |
|
|
|
//Append map |
|
function appendMap(error, us) { |
|
//Add counties |
|
CONSTANTS.COUNTIES = svgMap.append("g") |
|
.selectAll("path") |
|
.data(topojson.feature(us, us.objects.counties).features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("class", "county") |
|
.attr("id", function(d) { return 'state' + Math.floor(d.id / 1000)}) |
|
.on("mouseover", App.countyMouseover) |
|
.on("mouseout", App.countyMouseout); |
|
|
|
//Add states |
|
svgMap.append("path") |
|
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; })) |
|
.attr("class", "state") |
|
.attr("d", path); |
|
|
|
//Add circles |
|
CONSTANTS.BUBBLES = svgMap.append("g") |
|
.selectAll("circle") |
|
.data(topojson.feature(us, us.objects.counties).features) |
|
.enter().append("circle") |
|
.attr("class", "bubble") |
|
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; }) |
|
.attr("r", 1) |
|
.attr("id", function(d) { return _.uniqueId('bubble')}) |
|
.style("display", "none") |
|
.on("mouseover", App.bubbleMouseover) |
|
.on("mouseout", App.bubbleMouseout); |
|
|
|
} |
|
|
|
//RESPONSIVENESS |
|
d3.select(window).on('resize', resize); |
|
|
|
function resize() { |
|
var newDivWidth = d3.select(".map-container").node().clientWidth, |
|
newWidth = newDivWidth - margin.left - margin.right, |
|
newHeight = newWidth * mapRatio; |
|
|
|
var newProjection = d3.geo.albersUsa() |
|
.scale(newWidth * 1.325) |
|
.translate([newWidth/2, newHeight/2]); |
|
|
|
path = d3.geo.path() |
|
.projection(newProjection); |
|
|
|
svgMap |
|
.style('width', newWidth + 'px') |
|
.style('height', newHeight + 'px'); |
|
|
|
svgMap.selectAll('path').attr('d', path); |
|
|
|
svgMap.selectAll('circle').attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; }); |
|
} |
|
}, |
|
colorMap: function(demoLookup) { |
|
if ($('.switch').hasClass('noBubble')) { |
|
CONSTANTS.COUNTIES |
|
.style("fill", function(d) { |
|
var id = d.id.toString(); |
|
var fixedId = id.length === 5 ? id : "0" + id; |
|
if (fixedId in demoLookup) { |
|
return d3.interpolate('#d0d1e6', '#016450')(demoLookup[fixedId]['percentage']); |
|
} |
|
}); |
|
} |
|
}, |
|
uncolorMap: function(demoLookup) { |
|
CONSTANTS.COUNTIES |
|
.style("fill", "#f2f2f2") |
|
}, |
|
bubbleMap: function() { |
|
CONSTANTS.BUBBLES |
|
.attr("r", function(d) { |
|
var id = d.id.toString(); |
|
var fixedId = id.length === 5 ? id : "0" + id; |
|
if (fixedId in demoLookup) { |
|
return (demoLookup[fixedId]['percentage'] * 5); |
|
} |
|
}) |
|
.style("stroke", function(d) { |
|
var id = d.id.toString(); |
|
var fixedId = id.length === 5 ? id : "0" + id; |
|
if (fixedId in demoLookup) { |
|
return d3.interpolate('#d0d1e6', '#016450')(demoLookup[fixedId]['percentage']); |
|
} |
|
}) |
|
.style("fill", "transparent") |
|
.style("display", "block"); |
|
}, |
|
// searchCounties: function() { |
|
// if (!_.isObject(demoLookup)) { return; } |
|
// var userSearch = $('.county-search').val(); |
|
// userSearch = $.trim(userSearch); |
|
// var countyNames = _.pluck(demoLookup, "countyName"); |
|
// var matchedCounties = _.filter(countyNames, function(countyName) { |
|
// return fuzzysearch(userSearch, countyName) |
|
// }); |
|
// console.log(matchedCounties); |
|
// }, |
|
countyMouseover: function(e) { |
|
if (isHoverable == true && $('.switch').hasClass('noBubble')) { |
|
var countyId = e.id; |
|
d3.selection.prototype.moveToFront = function() { |
|
return this.each(function(){ |
|
this.parentNode.appendChild(this); |
|
}); |
|
}; |
|
var sel = d3.select(this); |
|
sel.moveToFront(); |
|
//Highlight county |
|
sel |
|
.transition().duration(100) |
|
.style({'stroke': '#262626', 'stroke-width': 2}); |
|
//Highlight state |
|
var thisState = Math.floor(e.id /1000) |
|
d3.selectAll('path:not(#state' + thisState + ')').style('opacity', .5); |
|
|
|
//tooltip |
|
var tooltip = d3.select(".tooltip") |
|
|
|
var right = d3.event.pageX > window.innerWidth/2; |
|
var offset = right ? tooltip.node().offsetWidth + 5 : 0; |
|
|
|
tooltip |
|
.transition().duration(100) |
|
.style('opacity', 1) |
|
.style('left', (d3.event.pageX - offset) + "px") |
|
.style('top', (d3.event.pageY + 10) + "px"); |
|
|
|
if (demoLookup && demoLookup[countyId]) { |
|
tooltip |
|
.html("<p class='bolded'>" + demoLookup[countyId]["countyName"] + ",</p><p class='bolded'>" + demoLookup[countyId]["stateName"] + "</p><p>" + (demoLookup[countyId]['percentage']*100).toFixed(1) + "%</p>"); |
|
} |
|
} |
|
}, |
|
bubbleMouseover: function(e) { |
|
if (isHoverable == true && $('.switch').hasClass('bubble')) { |
|
var countyId = e.id; |
|
d3.selection.prototype.moveToFront = function() { |
|
return this.each(function(){ |
|
this.parentNode.appendChild(this); |
|
}); |
|
}; |
|
var sel = d3.select(this); |
|
sel.moveToFront(); |
|
//Highlight bubble |
|
var thisBubble = $(this).attr('id') |
|
d3.selectAll('circle:not(#' + thisBubble + ')').style('opacity', 0.3); |
|
|
|
//tooltip |
|
var tooltip = d3.select(".tooltip") |
|
|
|
var right = d3.event.pageX > window.innerWidth/2; |
|
var offset = right ? tooltip.node().offsetWidth + 5 : 0; |
|
|
|
tooltip |
|
.transition().duration(100) |
|
.style('opacity', 1) |
|
.style('left', (d3.event.pageX - offset) + "px") |
|
.style('top', (d3.event.pageY + 10) + "px"); |
|
|
|
if (demoLookup && demoLookup[countyId]) { |
|
tooltip |
|
.html("<p class='bolded'>" + demoLookup[countyId]["countyName"] + ",</p><p class='bolded'>" + demoLookup[countyId]["stateName"] + "</p><p>" + (demoLookup[countyId]['percentage']*100).toFixed(1) + "%</p>"); |
|
} |
|
} |
|
}, |
|
bubbleMouseout: function(e) { |
|
var countyId = e.id; |
|
d3.selection.prototype.moveToBack = function() { |
|
return this.each(function() { |
|
var firstChild = this.parentNode.firstChild; |
|
if (firstChild) { |
|
this.parentNode.insertBefore(this, firstChild); |
|
} |
|
}); |
|
}; |
|
var sel = d3.select(this); |
|
sel.moveToBack(); |
|
//Dehighlight bubble |
|
var thisBubble = Math.floor(e.id / 1000) |
|
d3.selectAll('circle:not(#bubble' + thisBubble + ')').style('opacity', 1); |
|
//tooltip |
|
var tooltip = d3.select(".tooltip") |
|
.transition().duration(100) |
|
.style({'opacity': 0}); |
|
}, |
|
countyMouseout: function(e) { |
|
var countyId = e.id; |
|
d3.selection.prototype.moveToBack = function() { |
|
return this.each(function() { |
|
var firstChild = this.parentNode.firstChild; |
|
if (firstChild) { |
|
this.parentNode.insertBefore(this, firstChild); |
|
} |
|
}); |
|
}; |
|
var sel = d3.select(this); |
|
sel.moveToBack(); |
|
//Dehighlight county |
|
sel |
|
.transition().duration(100) |
|
.style({'stroke': 'white', 'stroke-width': 0.5}); |
|
//Dehighlight state |
|
var thisState = Math.floor(e.id / 1000) |
|
d3.selectAll('path:not(#state' + thisState + ')').style('opacity', 1); |
|
//tooltip |
|
var tooltip = d3.select(".tooltip") |
|
.transition().duration(100) |
|
.style({'opacity': 0}); |
|
} |
|
}; |
|
App.init(); |
|
}); |