Public
Edited
Mar 8, 2024
Insert cell
md`# Choropleth Mapping [GEOG 3540]
Median Age in Iowa Counties, Source: [Census 2010]`
Insert cell
md`## Heading 2`
Insert cell
md`This notebook creates a choropleth map.`
Insert cell
Insert cell
Insert cell
simple = require("simple-statistics@7.0.7/dist/simple-statistics.min.js")
Insert cell
format = d => `${d}%`
Insert cell
Insert cell
Maryland = FileAttachment("MarylandQGIS(1)(1).json").json()
Insert cell
counties = topojson.feature(Maryland, Maryland.objects.MarylandQGIS)
Insert cell
csv_data = d3.csvParse(await FileAttachment("Social_Charateristics_ObjectID.csv").text(),({OBJECTID, Total_Pop, Bachelors_degree}) => [+OBJECTID, (+Bachelors_degree/+Total_Pop)*100])
Insert cell
data = Object.assign(new Map(csv_data), {title: "Percentage of Adults with A Bachelor's Degree In Maryland"})
Insert cell
data.get(2)
Insert cell
degreepct = Array.from(csv_data.values(), d => d[1])
Insert cell
YlGnBu = [d3.color("#ffffcc"), d3.color("#c2e699"), d3.color("#78c679"), d3.color("#238443")]
Insert cell
naturalbreaks = simple.ckmeans(degreepct, YlGnBu.length).map(v => v.pop())
Insert cell
//more information on sequential scales: https://observablehq.com/@d3/sequential-scales
// color = d3.scaleSequentialQuantile([...data.values()], d3.interpolateBlues)

// color = d3.scaleQuantile()
// .domain(degreepct)
// .range(["#fee6ce", "#fdae6b", "#e6550d"])

color = d3.scaleThreshold()
.domain(naturalbreaks)
.range(YlGnBu)
Insert cell
width = 1250
Insert cell
height = 610
Insert cell
margin = 100
Insert cell
//Rotate the map sets the longitude of origin for our UTM Zone 15N projection.
projection = d3.geoTransverseMercator().rotate([78,0]).fitExtent([[80, 60], [width, height]], counties);
//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(137,495)")
.append(() =>
legend({
color: color,
title: data.title,
width: 310,
tickFormat: ".2f"
})
);

svg.append("g")
.selectAll("path")
.data(counties.features)
.join("path")
.attr("stroke", "#252525")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 0.8)
// .attr("fill", function(d){
// console.log(color(data.get(d.properties.FIPS)[0]))
// return color(data.get(d.properties.FIPS)[0]);
// })
.attr("fill", d => color(data.get(+d.properties.OBJECTID)))
.attr("d", path)
.append("title")
.text(d => " Degree Percentage: " + data.get(+d.properties.OBJECTID));

return svg.node();
}
Insert cell
data.get(2)
Insert cell
color(data.get(2))
Insert cell
color(40)
Insert cell
Insert cell
md`1. Join: In order to join csv attributes to the topojson features, they must both share a common attribute that can be used to bring them together. For this dataset, the attribute they share is OBJECTID. This means that within the csv attribute file, there is a column called OBJECTID that assigns each county in Maryland a different value from 1-24 based on alphebetical order. There is also a column within the topojson file named OBJECTID that assigns each feature a unique number from 1-24. Since these two columns match up, this attribute can be used to join them together. The two variables also have to match in terms of what kind of data they are. They both need to be integers or string, as it is not possible to join a string and an integer. In this case, the OBJECTID is an integer. To display this on the map, the line .data(counties.features) will pull the features from the topojson file, and then the line .attr("fill", d => color(data.get(+d.properties.OBJECTID))) will match the OBJECTID from the data function created from the csv file to the OBJECTID for each polygon, joining the two together on the map.
`
Insert cell
md`2.Mapping Values to colors: Colors can be choosen from ColorBrewer and entered as string to determine what color different classes should be. The line .attr("fill", d => color(data.get(+d.properties.OBJECTID))) is one of the lines of code that is responsible for using map values to determine color. The nested structure retrieves the data from the data function above, which matches OBJECTID to the percentage of people with bachelor's degree, ((Get(+d.properties.OBJECTID)). d => color will take the value and determine what color it should be based on the classification scale above. The domain of color is natural breaks, and the range of color is YlGnBu, which represents a color scheme picked from ColorBrewer. The value of each OBJECTID will be sorted into one of the four groups depending on the value of degreepct. `
Insert cell
md`3.Polygon Styling: The style, width, and color of the polygon borders can be modified by altering the values within the three lines of code underneath .join("path"). .attr("stroke", "#252525") alters the color of the outlines of the polygons. Here it is a dark grey color. .attr("stroke-linejoin", "round") modifies how the outlines of the polygons will interact when they connect to each other. .attr("stroke-width", 0.8) changes the width of the outline. `
Insert cell
md`4.Legend: The legend is drawn through .attr("transform", "translate(137,495)") and can be moved around within the map by changing the coordinates inside the translate function. The first number is the X-axis and the second one is the Y-axis. The legend can be modified by changing the values under .append(() => legend. The color, width, and title can all be modified. The number of decimal places that are displayed on the legend can also be changed by tickFormat: ".2f". The 2f means that two decimal places will be shown in the legend. `
Insert cell
md`5.Mouse Over: .text(d => " Degree Percentage: " + data.get(+d.properties.OBJECTID)); is the code that allows values to be displayed as text when a mouse is hovered over each polygon. The "Degree Percentage" is the text displayed before and then the degree percentage that corresponds with the OBJECTID of the polygon is displayed after. `
Insert cell
md`6.SVG: SVG stands for Scalable Vector Graphics and it allows for drawings to be made within a browser. It does not use pixels, but uses mathmatical formulas to create polygons and lines based on points and lines on a grid. The SVG fuction sets up the space that the map will be created on. `
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more