This is an interactive map chart written with d3.js v5 and based on the example from Interactive Data Visualization for the Web.
Last active
July 10, 2019 05:55
-
-
Save sabrinamochi/fc4e82bed4a612144b742d83db991d08 to your computer and use it in GitHub Desktop.
US Unemployment Rate in 2016
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<style> | |
.popuText, | |
.explainText, | |
.colorText, | |
.reset text, | |
.city_name { | |
font-size: 14px; | |
font-family: Arial, Helvetica, sans-serif; | |
} | |
.pan rect { | |
fill: none; | |
} | |
.colorText, | |
.reset text, | |
.zoom text, | |
.pan text { | |
fill: white; | |
} | |
.reset rect, | |
.zoom rect { | |
fill: black; | |
opacity: 0.3; | |
rx: 5; | |
/*rounded corners*/ | |
ry: 5; | |
} | |
.reset:hover rect, | |
.pan:hover rect, | |
.zoom:hover rect { | |
opacity: 0.5; | |
} | |
.reset:hover text, | |
.zoom:hover text, | |
.pan:hover text { | |
opacity: 1; | |
} | |
</style> | |
<title> us map </title> | |
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<h1>2016 Unemployment Rate Across the U.S.</h1> | |
<script type="text/javascript"> | |
var dcolor = [1, 2, 3, 4, 5] | |
var svg1 = d3.select('body') | |
.append('svg') | |
.attr('class', 'colorBar') | |
.attr('width', 1000) | |
.attr('height', 45); | |
var colorRect = svg1.selectAll('rect') | |
.data(dcolor) | |
.enter() | |
.append('rect') | |
.attr('x', function(d, i) { | |
return i * 100; | |
}) | |
.attr('y', 20) | |
.attr('width', 100) | |
.attr('height', 20); | |
colorRect.attr('fill', function(d, i) { | |
if (i == 0) { | |
return '#d8e0bb'; | |
} else if (i == 1) { | |
return '#b6cec7'; | |
} else if (i == 2) { | |
return '#86a3c3'; | |
} else if (i == 3) { | |
return '#7268a6'; | |
} else if (i == 4) { | |
return '#6b3074'; | |
} | |
}); | |
var colorText = svg1.selectAll('text') | |
.data(dcolor) | |
.enter() | |
.append('text') | |
.attr('class', 'colorText') | |
.attr('x', function(d, i) { | |
if (i == 0) { | |
return '0' | |
} else if (i == 4) { | |
return '470' | |
} | |
}) | |
.attr('y', 35) | |
.text(function(d, i) { | |
if (i == 0) { | |
return 'low' | |
} else if (i == 4) { | |
return 'high' | |
} | |
}) | |
.attr('font-style', 'italic') | |
var explainText = svg1.append('text') | |
.attr('class', 'explainText') | |
.text('Unemployment Rate') | |
.attr('x', 0) | |
.attr('y', 15) | |
.attr('fill', 'black') | |
.attr('font-style', 'italic'); | |
var circleData = [1, 2, 3, 4] | |
var populationCircle = svg1.selectAll('circle') | |
.data(circleData) | |
.enter() | |
.append('circle') | |
.attr('cx', function(d, i) { | |
return 600 + i * 40; | |
}) | |
.attr('cy', 30) | |
.attr('r', function(d, i) { | |
return 4 + i * 3; | |
}) | |
.style('fill', 'yellow') | |
.style('opacity', 0.9) | |
.style('stroke', 'gray') | |
.style('stroke-opacity', 0.2) | |
.style('stroke-width', 0.5); | |
var popuText = svg1.append('text') | |
.attr('class', 'popuText') | |
.text('City Population Size') | |
.attr('x', 595) | |
.attr('y', 15) | |
.attr('fill', 'black') | |
.attr('font-style', 'italic'); | |
var h = 500; | |
var w = 1000; | |
var projection = d3.geoAlbersUsa() | |
.translate([0, 0]); | |
var path = d3.geoPath() | |
.projection(projection); | |
//Create SVG element | |
var svg2 = d3.select("body") | |
.append("svg") | |
.attr('transform', 'translate(0, 0)') | |
.attr("width", w) | |
.attr("height", h); | |
var rowConverter = function(d) { | |
return { | |
state: d.state, | |
rate_medians: parseFloat(d.rate_medians) | |
}; | |
}; | |
var color = d3.scaleQuantize() | |
.range(['#d8e0bb', '#b6cec7', '#86a3c3', '#7268a6', '#6b3074']); | |
// define what to do when dragging | |
var dragging = function(d) { | |
// get the present translation offset | |
var offset = projection.translate(); | |
// d3.event only exists in context, during an event | |
// dx,dy contain the amount of change in x,y position | |
//since the preceding drag event | |
offset[0] += d3.event.dx; | |
offset[1] += d3.event.dy; | |
// update projection with new offset | |
projection.translate(offset); | |
//update all paths and circles | |
map.selectAll('path') | |
.attr('d', path); | |
map.selectAll('circle') | |
.attr('cx', function(d) { | |
return projection([d.lon, d.lat])[0]; | |
}) | |
.attr('cy', function(d) { | |
return projection([d.lon, d.lat])[1]; | |
}); | |
}; | |
var drag = d3.drag() | |
.on('drag', dragging); | |
var zoomed = function(d) { | |
var offset = projection.translate(); | |
offset[0] = d3.event.transform.x; | |
offset[1] = d3.event.transform.y; | |
var newScale = d3.event.transform.k * 2000; | |
projection.translate(offset) | |
.scale(newScale); | |
//update all paths and circles | |
svg2.selectAll('path') | |
.attr('d', path); | |
svg2.selectAll('circle') | |
.attr('cx', function(d) { | |
return projection([d.lon, d.lat])[0]; | |
}) | |
.attr('cy', function(d) { | |
return projection([d.lon, d.lat])[1]; | |
}); | |
}; | |
var zoom = d3.zoom() | |
.translateExtent([ | |
[-1200, -700], | |
[1200, 700] | |
]) | |
.scaleExtent([0.3, 2.0]) | |
.on('zoom', zoomed) | |
// put all dragable elements in one group | |
var map = svg2.append('g') | |
.attr('id', 'map') | |
.call(zoom) // Bind the zoom behavior | |
.call(zoom.transform, d3.zoomIdentity // apply the initial transform | |
.translate(w / 2, h / 2) | |
.scale(0.45)); | |
map.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('width', w) | |
.attr('height', h) | |
.attr('opacity', 0); | |
// load in unemployment rate data | |
d3.csv('https://raw.githubusercontent.com/sabrinamochi/us_unemployment/master/us_unemployment/us_unemployment_rate.csv', rowConverter).then(function(data) { | |
var dataset = data; | |
console.table(dataset, ['state', 'rate_medians']); | |
color.domain([ | |
d3.min(dataset, function(d) { | |
return d.rate_medians; | |
}), | |
d3.max(dataset, function(d) { | |
return d.rate_medians; | |
}) | |
]); | |
// load in GeoJSON data | |
d3.json('https://raw.githubusercontent.com/alignedleft/d3-book/master/chapter_14/us-states.json').then(function(json) { | |
// merge the unemployment data and GeoJSON data | |
for (var i = 0; i < dataset.length; i++) { | |
var dataState = dataset[i].state; | |
var dataValue = dataset[i].rate_medians; | |
// find the corresponding state name | |
for (var j = 0; j < json.features.length; j++) { | |
var jsonState = json.features[j].properties.name; | |
if (dataState == jsonState) { | |
// Copy the data value into the JSON | |
json.features[j].properties.value = dataValue; | |
break; | |
} | |
} | |
} | |
map.selectAll('path') | |
.data(json.features) | |
.enter() | |
.append('path') | |
.attr('d', path) | |
.style('fill', function(d) { | |
var value = +d.properties.value; | |
if (value) { | |
// if value exists | |
return color(value); | |
} else { | |
// if value is undefined | |
return '#ccc'; | |
} | |
}); | |
// load in cities data | |
d3.csv('https://raw.githubusercontent.com/alignedleft/d3-book/master/chapter_14/us-cities.csv').then(function(data) { | |
map.selectAll('circle') | |
.data(data) | |
.enter() | |
.append('circle') | |
.attr('cx', function(d) { | |
return projection([d.lon, d.lat])[0]; | |
}) | |
.attr('cy', function(d) { | |
return projection([d.lon, d.lat])[1]; | |
}) | |
.attr('r', function(d) { | |
return Math.sqrt(parseInt(d.population) * 0.00004); | |
}) | |
.style('fill', 'yellow') | |
.style('opacity', 0.75) | |
/*var text = svg2.selectAll('text') | |
.data(data) | |
.enter() | |
.append('text') | |
.attr('class', 'city_name') | |
.text(function(d){ | |
if (parseFloat(d.population) > 1000000) { | |
return d.place;} | |
}) | |
.attr('x', function (d) { | |
return projection([d.lon, d.lat])[0]; | |
}) | |
.attr('y', function (d) { | |
return projection([d.lon, d.lat])[1]-8;}) | |
.style('fill', '#D8D6D8');*/ | |
//createPanButtons(); | |
createScaleButtons(); | |
createResetButton(); | |
}); | |
}); | |
}); | |
var createPanButtons = function() { | |
// create the clickable groups | |
// north | |
var north = svg2.append('g') | |
.attr('class', 'pan') | |
.attr('id', 'north'); | |
north.append('rect') | |
.attr('x', 40) | |
.attr('y', 0) | |
.attr('width', w - 80) | |
.attr('height', 40); | |
north.append('text') | |
.attr('x', w / 2) | |
.attr('y', 25) | |
.html('↑') | |
//south | |
var south = svg2.append('g') | |
.attr('class', 'pan') | |
.attr('id', 'south') | |
south.append('rect') | |
.attr('x', 40) | |
.attr('y', h - 40) | |
.attr('width', w - 80) | |
.attr('height', 40); | |
south.append('text') | |
.attr('x', w / 2) | |
.attr('y', h - 15) | |
.html('↓'); | |
//west | |
var west = svg2.append('g') | |
.attr('class', 'pan') | |
.attr('id', 'west') | |
west.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('width', 40) | |
.attr('height', h + 40); | |
west.append('text') | |
.attr('x', 15) | |
.attr('y', h / 2) | |
.html('←'); | |
//east | |
var east = svg2.append('g') | |
.attr('class', 'pan') | |
.attr('id', 'east') | |
east.append('rect') | |
.attr('x', w - 40) | |
.attr('y', 0) | |
.attr('width', 40) | |
.attr('height', h + 40); | |
east.append('text') | |
.attr('x', w - 25) | |
.attr('y', h / 2) | |
.html('→'); | |
// panning interaction | |
d3.selectAll('.pan') | |
.on('click', function() { | |
var x = 0; | |
var y = 0; | |
var direction = d3.select(this).attr('id'); | |
var moveAmount = 50; | |
switch (direction) { | |
case 'north': | |
y += moveAmount; | |
break; | |
case 'south': | |
y -= moveAmount; | |
break; | |
case 'east': | |
x -= moveAmount; | |
break; | |
case 'west': | |
x += moveAmount; | |
break; | |
default: | |
break; | |
}; | |
map.transition() | |
.call(zoom.translateBy, x, y); | |
}); | |
} | |
var createScaleButtons = function() { | |
// zoom in button | |
var zoomIn = svg2.append('g') | |
.attr('class', 'zoom') | |
.attr('id', 'in') | |
.attr('transform', 'translate(' + (w - 140) + ', 290)'); | |
zoomIn.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('width', 30) | |
.attr('height', 30); | |
zoomIn.append('text') | |
.attr('x', 10) | |
.attr('y', 20) | |
.text('+'); | |
var zoomOut = svg2.append('g') | |
.attr('class', 'zoom') | |
.attr('id', 'out') | |
.attr('transform', 'translate(' + (w - 100) + ',290)'); | |
zoomOut.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('width', 30) | |
.attr('height', 30); | |
zoomOut.append('text') | |
.attr('x', 10) | |
.attr('y', 20) | |
.text('-'); | |
d3.selectAll('.zoom') | |
.on('click', function() { | |
var scaleFactor; | |
var direction = d3.select(this).attr('id'); | |
switch (direction) { | |
case 'in': | |
scaleFactor = 1.5; | |
break; | |
case 'out': | |
scaleFactor = 0.75; | |
break; | |
default: | |
break; | |
}; | |
map.transition() | |
.call(zoom.scaleBy, scaleFactor); | |
}); | |
}; | |
var createResetButton = function() { | |
var reset = svg2.append('g') | |
.attr('class', 'reset') | |
.attr('transform', 'translate(' + (w - 140) + ',250)'); | |
reset.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('width', 70) | |
.attr('height', 30); | |
reset.append('text') | |
.attr('x', 15) | |
.attr('y', 20) | |
.text('Reset') | |
d3.select('.reset') | |
.on('click', function() { | |
map.transition() | |
.call(zoom.transform, d3.zoomIdentity // apply the initial transform | |
.translate(w / 2, h / 2) | |
.scale(0.45)); | |
}); | |
}; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment