Public
Edited
Apr 23, 2023
Insert cell
md`# Ronan's Choropleth Mapping
Percentage of Seniors in Georgia, Source: [Census 2010]`
Insert cell
d3 = require("d3@5")
//Importing libraries
Insert cell
import {legend} from "@d3/color-legend"//
//Importing a legend that is not included in the original libraries
Insert cell
simple = require("simple-statistics@7.0.7/dist/simple-statistics.min.js")
//importing simple statistics library. This will be used for natural breaks.
Insert cell
format = d => `${d}%`
//This format function takes an input and formats the number in a different manner.
Insert cell
topojson = require("topojson-client@3")
//Importing the topojson library
Insert cell
Georgia = FileAttachment("Georgia.json").json()
//Importing and referencing the Json file
//Incidentally, before we start coding, to make sure that the geometries are included from both topojson and csv file, we create a join in QGIS. We right-click on the file, click join and add a target field. This will then create a layer over the map with the attributes from the CSV file. Save it and export it as a geojson and transform it into a topojson with mapshaper.
Insert cell
CensusFeatures = topojson.feature(Georgia, Georgia.objects.Georgia)
//Creating the features
Insert cell
csv_data = d3.csvParse(await FileAttachment("FoodAccessResearchAtlasData2019.csv").text(),({CensusTract, Pop2010, TractSeniors}) => [CensusTract, (+TractSeniors, ((+TractSeniors)/+Pop2010)*100)])
//Creating a new variable which measures the percentage of senior population for each Census tract.
Insert cell
data = Object.assign(new Map(csv_data), {title: "Percentage of Seniors in Georgia"})
//Assigning each Census tract with its corresponding value in percentage
Insert cell
SeniorsPCT = Array.from(csv_data.values(), d => d[1])
//SeniorsPCT creates an array of each ot the percentages
Insert cell
YlOrRd = [d3.color("#ffffb2"), d3.color("#fecc5c"), d3.color("#fd8d3c"), d3.color("#de2d26"), d3.color("a50f15")]
//This code essentially creates colors in Observable. YLOrRD is a 5-class multi-hue sequential color scheme I found on colorbrewer.org. This choice made sense as it doesn't highlight outliers as much as unclassed, for example, and it made sense to not use a diverging color scheme since it's not binary. These colors will later be assigned to their respective class.
Insert cell
naturalbreaks = simple.ckmeans(SeniorsPCT, YlOrRd.length).map(v => v.pop())
//The naturalbreaks function here uses kmeans clustering to create 4 natural breaks in my data. We can see that the lowest value is 6.467 and the highes 47.653 with the data leaning to the left.
Insert cell
//more information on sequential scales: https://observablehq.com/@d3/sequential-scales
// color = d3.scaleSequentialQuantile([...data.values()], d3.interpolateBlues)

// color = d3.scaleQuantile()
// .domain(med_age)
// .range()

color = d3.scaleThreshold()
.domain(naturalbreaks.slice(0, -2))
.range(YlOrRd)

// This code uses the above created naturalbreaks and assigns it to the variable color. This means that each of the bins created will get one of the five colors within the YLOrRd palette. This is important so that later on the variable color can be used to create a choropleth map. I used slice (0,-2) to deemphasize the top two values in my data set that were incorrect. This allows for the black parts to disappear.
Insert cell
width = 975
Insert cell
height = 610
Insert cell
margin = 100
//The three cells above set the size for the output.
Insert cell
//Rotate the map sets the longitude of origin for our UTM Zone 15N projection.
projection = d3.geoTransverseMercator().rotate([90,0]).fitExtent([[80, 80], [width, height]], CensusFeatures);
//As the professor mentioned, the above code for projection is used to make sure that our CensusFeatures are oriented correctly before creating a Choropleth map.
//d3 reference for projections: https://github.com/d3/d3-geo/blob/master/README.md

//use the following url for specific projection settings: https://github.com/veltman/d3-stateplane
//Use this code to set up the map projection (if different than geographic projection)

//projection = d3.geoAlbers().fitExtent([[margin, margin], [width - margin, height - margin]], counties)

//projection = d3.geoMercator().fitExtent([[margin, margin], [width - margin, height - margin]], counties)
Insert cell
//Using a path generator to project geometry onto the map
path = d3.geoPath().projection(projection);
Insert cell
choropleth = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

svg.append("g")
.attr("transform", "translate(360,20)")
.append(() =>
legend({
color: color,
title: data.title,
width: 260,
tickFormat: ".1f"
})
);
//These last 6 lines of code create the legend.
svg.append("g")
.selectAll("path")
.data(CensusFeatures.features)
.join("path")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 0.25)
// .attr("fill", function(d){
// console.log(color(data.get(d.properties.FIPS)[0]))
// return color(data.get(d.properties.FIPS)[0]);
// })
//These approx 10 line of code are where we determine the style of the borders and outline.
.attr("fill", d => color(data.get(d.properties.GEOID)))
.attr("d", path)
.append("title")
.text(d => " SeniorsPCT: " + data.get(d.properties.GEOID));
//This last line lets you hover over a tract and retrieve the information.
return svg.node();
}

//This box of code is used to combine multiple things we created further up and to finally create the choropleth map. Initially we created a a constant variable called svg which stands for scalable vector graphics. Then we assigned a color, title and a size to the legend. Next, we joined the created path, which projects geometry onto a map, with our CensusFeatures variable and gave the lines a specific color and width. Finally, we give the map its color which is from our YlOrRD color scheme. Finally, we project a title onto the map. It is important to use the matching column in from our topojson and csvfile. In this instance it was the GEOID.
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more