Public
Edited
Apr 18, 2024
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

svg.append(legend)
.attr("transform", "translate(240,360)");
//Generates the legend onto the canvas, The two numbers determine the location of the legend on the map. They can be changed accordingly so that the legend is not behind the map.

svg.append("g")
.selectAll("path")
.data(topojson.feature(polygons, polygons.objects.MarylandQGIS).features)
.join("path")
.attr("stroke", "#808080")
.attr("stroke-width", 0.4)
//Displays the borders of each county. The hex code is the color of the borders and the width is how thick each border line is.
.attr("fill", d => color(data.get(d.properties[idAttribute])))
.attr("d", path)
.append("title")
.text(d => `${d.properties[idAttribute]}, ${format(data.get(d.properties[idAttribute]))}`);
//Displays the county name and the two variables when the mouse is hovered over a county on the map.

return svg.node();
}
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/

//This section is where you would add a new bivariate color scheme. There are four different color schemes already made, but to make a new one, you would just have to change the name and the hex codes for the 9 different colors present in a bivariate color scheme.

//Not all color schemes are suitable for bivariate maps. It is best to have a 3-class sequential color scheme for each variable, where the lightest colors represent the lowest values, and the darkest and most saturated colors represent high values. The lightest color should be the same across both color schemes. When overlayed on top of each other, the two color schemes should create 9 unique color combinations. The darkest color will represent when both variables are high, and the lightest color will represent when both variables are low.

schemes = [
{
name: "RdBu",
colors: [
"#e8e8e8", "#e4acac", "#c85a5a",
"#b0d5df", "#ad9ea5", "#985356",
"#64acbe", "#627f8c", "#574249"
]
},
{
name: "BuPu",
colors: [
"#e8e8e8", "#ace4e4", "#5ac8c8",
"#dfb0d6", "#a5add3", "#5698b9",
"#be64ac", "#8c62aa", "#3b4994"
]
},
{
name: "GnBu",
colors: [
"#e8e8e8", "#b5c0da", "#6c83b5",
"#b8d6be", "#90b2b3", "#567994",
"#73ae80", "#5a9178", "#2a5a5b"
]
},
{
name: "PuOr",
colors: [
"#e8e8e8", "#e4d9ac", "#c8b35a",
"#cbb8d7", "#c8ada0", "#af8e53",
"#9972af", "#976b82", "#804d36"
]
}
]
Insert cell
//Each color defines a value as being low, medium, or high.
labels = ["low", "Medium", "high"]
Insert cell
n = Math.floor(Math.sqrt(colors.length))
Insert cell
x = d3.scaleQuantile(Array.from(data.values(), d => d[0]), d3.range(n))
Insert cell
y = d3.scaleQuantile(Array.from(data.values(), d => d[1]), d3.range(n))
Insert cell
path = d3.geoPath().projection(projection)
Insert cell
//Rotate the map sets the longitude of origin for our UTM projection.
projection = d3.geoTransverseMercator().rotate([87,0]).fitExtent([[10, 10], [width, height]], polygon_features);
Insert cell
polygon_features = topojson.feature(polygons, polygons.objects.MarylandQGIS)
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("Social_Charateristics@3.csv").text(), ({county, Unemployment_Rate, HighSchool_NoDiploma, Total_Pop}) => [county, [+Unemployment_Rate, 100*+HighSchool_NoDiploma/+Total_Pop]])), {title: ["Unemployment Rate", "No High school Diploma"]})
Insert cell
//The idAttribute is how the data from the csv file is joined with the geometries that come with the topojson file. In both the csv file and the topojson file there is the unique name of each county in Maryland. This attribute was used to join them together. To join them properly, make sure that the names are spelled the same with the same punctuation in both files.
idAttribute = "county"
Insert cell
polygons = FileAttachment("MarylandQGIS(1)(1).json").json()
Insert cell
height = 610
Insert cell
width = 875
Insert cell
legend = () => {
const k = 32;
//Defines how large the legend will be on the map.
const arrow = DOM.uid();
return svg`<g font-family=sans-serif font-size=10>
<g transform="translate(-${k * n / 2},-${k * n / 2}) rotate(-45 ${k * n / 2},${k * n / 2})">
<marker id="${arrow.id}" markerHeight=10 markerWidth=10 refX=6 refY=3 orient=auto>
<path d="M0,0L9,3L0,6Z" />
</marker>
${d3.cross(d3.range(n), d3.range(n)).map(([i, j]) => svg`<rect width=${k} height=${k} x=${i * k} y=${(n - 1 - j) * k} fill=${colors[j * n + i]}>
<title>${data.title[0]}${labels[j] && ` (${labels[j]})`}
${data.title[1]}${labels[i] && ` (${labels[i]})`}</title>
</rect>`)}
<line marker-end="${arrow}" x1=0 x2=${n * k} y1=${n * k} y2=${n * k} stroke=black stroke-width=1.5 />
<line marker-end="${arrow}" y2=0 y1=${n * k} stroke=black stroke-width=1.5 />
<text font-weight="bold" dy="0.71em" transform="rotate(90) translate(${n / 2 * k},6)" text-anchor="middle">${data.title[0]}</text>
<text font-weight="bold" dy="0.71em" transform="translate(${n / 2 * k},${n * k + 6})" text-anchor="middle">${data.title[1]}</text>
</g>
</g>`;
//The above section defines what font and how large the legend text will be and rotates the legend and the titles (for readability purposes).
}
Insert cell
color = {
return value => {
if (!value) return "#000000";
//any null values will show up on the map as the color black.
let [a, b] = value;
return colors[y(b) + x(a) * n];
//Assigns values to colors based on the relation to other numbers within the variable's dataset.
};
}
Insert cell
formatNum = d3.format(".1f")
Insert cell
format = (value) => {
if (!value) return "N/A";
let [a, b] = value;
return `${a} ${data.title[0]}${labels[x(a)] && ` (${labels[x(a)]})`}
${formatNum(b)} ${data.title[1]}${labels[y(b)] && ` (${labels[y(b)]})`}`;
}
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@5")
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