Making a Map¶
Let’s talk about making a map in D3. D3 is GREAT for making maps and it pretty straightforward to do so.
Let’s create a [choropleth map](https://en.wikipedia.org/wiki/Choropleth_map#:~:text=A choropleth map (from Greek,each area, such as population) for PA.
A really good resource for this: https://observablehq.com/@floledermann/drawing-maps-from-geodata-in-d3
from IPython.display import HTML, Javascript, display
def configure_d3():
display(Javascript("""
require.config({
paths: {
d3: "https://d3js.org/d3.v6.min"
}
})"""))
configure_d3()
Let’s look at our data¶
%%html
<div id="output1"></div>
<div id="output2"></div>
<script type="text/javascript">
require(['d3'], function (d3) {
d3.json("https://gist.githubusercontent.com/dudaspm/89da9e990236d2bc73a3a6ee00c18bb6/raw/055b78a9d016873c36e28872ab6a4d85d0be858e/usCounties.json")
.then(function(us) {
d3.select("div#output1").text('{"type":"FeatureCollection", "features": []}')
d3.select("div#output2").text(JSON.stringify(us["features"][0],null,'\t'))
})
.catch(function(error){
console.log(error)
})
})
</script>
Creating the Projection for the US¶
%%html
<div id="map1"></div>
<script type="text/javascript">
require(['d3'], function (d3) {
d3.json("https://gist.githubusercontent.com/dudaspm/89da9e990236d2bc73a3a6ee00c18bb6/raw/055b78a9d016873c36e28872ab6a4d85d0be858e/usCounties.json")
.then(function(us) {
const width = 800
const height = 600
const margin = 0
// Create the Mercator Projection
projectionUS = d3.geoMercator().fitExtent([[margin, margin], [width - margin, height - margin]], us)
// Create a function to generate our paths (counties)
pathGeneratorUS = d3.geoPath().projection(projectionUS)
const svg = d3.select("div#map1").append("svg")
.attr("width", width)
.attr("height", height)
// construct the element
svg.selectAll("path")
.data(us.features)
.join("path")
.attr('d', pathGeneratorUS)
.attr('fill', 'none')
.attr('stroke', '#999999')
.attr('stroke-width', '2')
})
.catch(function(error){
console.log(error)
})
})
</script>
This is a county map for the entire US. Let’s focus on just Pennsylvania. To do this, we need to filter to PA’s data.
If we take a look at the data. The data contains a stateFP for the FIPS for each state.
For PA it is 42.
%%html
<div id="map2"></div>
<script type="text/javascript">
require(['d3'], function (d3) {
d3.json("https://gist.githubusercontent.com/dudaspm/89da9e990236d2bc73a3a6ee00c18bb6/raw/055b78a9d016873c36e28872ab6a4d85d0be858e/usCounties.json")
.then(function(us) {
const width = 800
const height = 600
const margin = 0
// filter data to only PA
pa = ({type:"FeatureCollection", features:us.features.filter(d=> d.properties.STATEFP==42)})
// Create the Mercator Projection
projectionPA = d3.geoMercator().fitExtent([[margin, margin], [width - margin, height - margin]], pa)
// Create a function to generate our paths (counties)
pathGeneratorPA = d3.geoPath().projection(projectionPA)
const svg = d3.select("div#map2").append("svg")
.attr("width", width)
.attr("height", height)
// construct the element
svg.selectAll("path")
.data(pa.features)
.join("path")
.attr('d', pathGeneratorPA)
.attr('fill', 'white')
.attr('stroke', '#999999')
.attr('stroke-width', '2')
.append("title")
.text(d=> JSON.stringify(d.properties,null,'\t'))
})
.catch(function(error){
console.log(error)
})
})
</script>
Let’s get some data to add to our map.
Example is from Los Angeles Times Data and Graphics Department
An example of using data from the Data Desk’s open-source census-data-downloader
%%html
<div id="output3"></div>
<div id="output4"></div>
<script type="text/javascript">
require(['d3'], function (d3) {
d3.csv("https://raw.githubusercontent.com/datadesk/census-data-downloader/613e69f6413d917a6db60186e5ddf253e722dcfd/data/processed/acs5_2017_internet_counties.csv")
.then(function(census) {
d3.select("div#output4").text(JSON.stringify(census[0],null,'\t'))
})
.catch(function(error){
console.log(error)
})
})
</script>
So, based on our data we have a state and a county code (like above). Let’s use this data to see the percentage of internet access per county in PA.
%%html
<div id="map3"></div>
<div id="legend1"></div>
<script type="text/javascript">
require(['d3'], function (d3) {
d3.json("https://gist.githubusercontent.com/dudaspm/89da9e990236d2bc73a3a6ee00c18bb6/raw/055b78a9d016873c36e28872ab6a4d85d0be858e/usCounties.json")
.then(function(us) {
d3.csv("https://raw.githubusercontent.com/datadesk/census-data-downloader/613e69f6413d917a6db60186e5ddf253e722dcfd/data/processed/acs5_2017_internet_counties.csv")
.then(function(census) {
const width = 800
const height = 600
const margin = 0
// filter data to only PA
pa = ({type:"FeatureCollection", features:us.features.filter(d=> d.properties.STATEFP==42)})
// Adding our census data
census = census.filter(d=> d.state==42)
censusObject = {}
census.forEach(d=> censusObject[d.county] = (+d.total_no_internet/+d.universe))
// create our color
palette = d3.interpolatePurples
color = d3.scaleLinear().range([0,1]).domain(d3.extent(census,d=> (+d.total_no_internet/+d.universe)))
// Create the Mercator Projection
projectionPA = d3.geoMercator().fitExtent([[margin, margin], [width - margin, height - margin]], pa)
// Create a function to generate our paths (counties)
pathGeneratorPA = d3.geoPath().projection(projectionPA)
const svg = d3.select("div#map3").append("svg")
.attr("width", width)
.attr("height", height)
console.log(censusObject)
// construct the element
svg.selectAll("path").append('path')
.data(pa.features)
.join("path")
.attr('d', pathGeneratorPA)
.attr('fill', d=> palette(color(censusObject[d.properties.COUNTYFP])))
.attr('stroke', '#999999')
.attr('stroke-width', '2')
.append("title")
.text(d=> "Location: "+d.properties.NAME+"\nData: "+censusObject[d.properties.COUNTYFP])
})
.catch(function(error){
console.log(error)
})
})
.catch(function(error){
console.log(error)
})
})
</script>
%%html
<script type="text/javascript">
require(['d3'], function (d3) {
d3.csv("https://raw.githubusercontent.com/datadesk/census-data-downloader/613e69f6413d917a6db60186e5ddf253e722dcfd/data/processed/acs5_2017_internet_counties.csv")
.then(function(census) {
const width = 800
const height = 100
const margin = 40
census = census.filter(d=> d.state==42)
palette = d3.interpolatePurples
x = d3.scaleLinear().range([margin,width-margin]).domain(d3.extent(census,d=> (+d.total_no_internet/+d.universe)))
const svg = d3.select("div#legend1").append("svg")
.attr("width", width)
.attr("height", height)
const xAxis = d3.axisBottom().scale(x)
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (height-margin) + ")")
.call(xAxis)
svg.append("text")
.attr("x", width/2)
.attr("y", height-5)
.style("text-anchor", "middle")
.text("No Internet/All Data (%)")
const num = 20
const values = d3.range(1,num)
const coloring = d3.scaleLinear().range([0,1]).domain(d3.extent(values))
var defs = svg.append("defs")
var linearGradient = defs.append("linearGradient")
.attr("id", "linear-gradient1")
linearGradient.selectAll("stop").data(values).join("stop")
.attr("offset", d=> d/num)
.attr("stop-color", d=>palette(coloring(d)) )
svg.append("rect")
.attr("x", margin)
.attr("y", (height-margin)-50)
.attr("width", (width-margin)-(margin))
.attr("height", 50)
.style("fill", "url(#linear-gradient1)")
})
.catch(function(error){
console.log(error)
})
})
</script>