Public
Edited
Apr 10, 2023
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(670,250)"); //This adds the legend to the map at the coordinates and ensures it's not buried underneath the map.

svg.append("g")
.selectAll("path")
.data(topojson.feature(polygons, polygons.objects.Idaho_Counties).features)
.join("path")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("stroke-width", .5)
.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]))}`);

return svg.node();
}
Insert cell
idaho_counties = FileAttachment("Idaho_Counties.json").json()
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/
schemes = [
{
name: "PuOr",
colors: [
"#e8e8e8", "#e4d9ac", "#c8b35a",
"#cbb8d7", "#c8ada0", "#af8e53",
"#9972af", "#976b82", "#804d36"
]
},
{
name: "GnBu",
colors: [
"#e8e8e8", "#b5c0da", "#6c83b5",
"#b8d6be", "#90b2b3", "#567994",
"#73ae80", "#5a9178", "#2a5a5b"
]
},
{
name: "BuPu",
colors: [
"#e8e8e8", "#ace4e4", "#5ac8c8",
"#dfb0d6", "#a5add3", "#5698b9",
"#be64ac", "#8c62aa", "#3b4994"
]
},
{
name: "RdBu",
colors: [
"#e8e8e8", "#e4acac", "#c85a5a",
"#b0d5df", "#ad9ea5", "#985356",
"#64acbe", "#627f8c", "#574249"
]
}
]
//This function creates the color schemes used in the map above. Each object within the array contains 9 colors used for mapping. You need 9 colors for the map because we are mapping 2 variables with 3 classes each (low, medium, high) which produces 9 classes total. each class representing the relationship of the 2 variables classes'. The colors are specifically designed to be sequential bivariate schemes to represent each class effectively. You don't have a lot of options when it comes to color schemes for this type of map due to the relatedness of the data and what you are trying to display.
Insert cell
labels = ["low", "med", "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([114,0]).fitExtent([[10, 10], [width, height]], polygon_features);
Insert cell
polygon_features = topojson.feature(polygons, polygons.objects.Idaho_Counties)
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("IdahoCountiesUpdated.csv").text(), ({NAME, AGE_UNDER15, AGE_15_64, AGE_OVER65}) => [NAME, [100*(+AGE_UNDER15/+AGE_15_64), 100*(+AGE_OVER65/+AGE_15_64)]])), {title: ["Youth Ratio %", "Old-Age Ratio %"]})
Insert cell
idAttribute = "NAME"
Insert cell
polygons = FileAttachment("Idaho_Counties.json").json()
Insert cell
height = 610
Insert cell
width = 875
Insert cell
legend = () => {
const k = 24;
const arrow = DOM.uid(); //Creates the arrows seen above that indicate low to high
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>`;
}
//This function creates the legend seen above. The function utilitzes our "colors" function containing the 4 color schemes to fill the small boxes with the proper color. The function is quite complex and specifies a variety of things such as the text style, position, and boldness as well as the arrows color and size.
Insert cell
color = {
return value => {
if (!value) return "#ccc";
let [a, b] = value;
return colors[y(b) + x(a) * n];
};
}
Insert cell
formatNum = d3.format(".1f")
Insert cell
format = (value) => {
if (!value) return "N/A"; //If there isn't a value it will display "N/A"
let [a, b] = value;
return `${formatNum(a)} ${data.title[0]}${labels[x(a)] && ` (${labels[x(a)]})`}
${formatNum(b)} ${data.title[1]}${labels[y(b)] && ` (${labels[y(b)]})`}`;
}
//This function creates a "hover over" function so when you hover over a county, or polygon feature, it will display the values of each variable as well as our assigned labels of high or low. The first line in the return sets all of the display criteria for variable "a" such as how the number is formatted (decimal places), the text, and the label assigned to the value if applicable. The second line does the same thing but instead for variable "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