Public
Edited
May 9
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("style", "background: white");
// Creates the SVG canvas, which the map will be displayed upon. I also added code to change the background color of the map, which I left as white, as this looks best with the colors on the map. The color can be changed by implementing a new hex code in place of "white".
svg.append(legend)
.attr("transform", "translate(375,250)");
// Sets the location of the legend, which I moved over to 375 so that it would be positioned in a good looking spot in relation to the shape of Florida. The legend can be moved by changing the values in the parenthesis after "translate" to position it in a location that is visible and looks good.
svg.append("g")
.selectAll("path")
.data(topojson.feature(counties, counties.objects.ProjectedNRISHP).features)
// Reads the polygons in the topojson and maps them to the SVG canvas
.join("path")
.attr("fill", d => color(data.get(d.properties[idAttribute])))
// Assigns the color schemes, created below, to the polygons and fills them according to the values of the variables, which are scaled in the quantile data calssification scheme.
.attr("stroke", "black")
.attr("stroke-width", 0.25)
// I added strokelines to the code to make the county polygons more distinguishable.
.attr("d", path)
.append("title")
.text(d => `${d.properties[idAttribute]}, ${format(data.get(d.properties[idAttribute]))}`);
// This portion of the line creates the hover over feature. It takes the properties associated with the "idAttribute" line and reads them as you hover your mouse over a county polygon. In my case, it displays the name of the county, the populaiton density, and the percentage of vacant homes in a county. It does so by pulling the values connected to each unique identifier (as well as the unique identifier itself) from the data line to this line. It then assigns the values a "high" or "low" attribute according to where each variable's value lies in the quantile scale.

return svg.node();
}
Insert cell
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/
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"
]
}
]
// Defines four different bivariate color schemes to the map, which can be switched between for preferred visualization. If one wishes to include a new color scheme, the colors chosen should reflect the nature of the data used. For bipolar data, a diverging color scheme should be used and for unipolar data, a sequential color scheme should be used. These color schemes, with hex codes, can be found on the the website link at the top of this line of code. The name should reflect the main hues in the color scheme and the hexcodes can be added, in the correct order according to the sequntial (or divergent) order of them, in the brackets after "colors:".
Insert cell
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))
// Scales the x variable (percentage of vacant households) to the quantile data classification scheme.
Insert cell
y = d3.scaleQuantile(Array.from(data.values(), d => d[1]), d3.range(n))
// Scales the y variable (population density) to the quantile data classification scheme.
Insert cell
path = d3.geoPath().projection(projection)
// Paths the projection to the map.
Insert cell
height = 610
Insert cell
width = 975
Insert cell
margin = 50
Insert cell
projection = d3.geoAlbersUsa().fitExtent([[margin, margin], [width - margin, height - margin]], counties)
// Defines the projection of the map, in this case it projects florida in a Universal Transverse Mercator projection.
Insert cell
polygon_features = topojson.feature(counties, counties.objects.ProjectedNRISHP)
// paths the projection (function above) to the
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("ultramaster.csv").text(), ({STCOFIPS, precipanomaly19751980, tempanomaly19751980}) => [STCOFIPS, [+precipanomaly19751980, +tempanomaly19751980]])), {title: ["Temp Anomaly", "Precip Anomaly"]})
// Reads the .csv file and maps the unique identifier (in this case the name of the county) to the variables I wished to include in my map. This line is also used to standardize the raw data values from the .csv file, which is necessary for a good bivariate map. The final thing that this line of code does is titling the legend on the map.
Insert cell
idAttribute = "STCOFIPS"
// Defines the unique identifier, which will be used to join the .csv data to the polygons within the topojson features. This is later used in the SVG canvas line to fill the colors on the map and to create the hover over feature.
Insert cell
counties = FileAttachment("ProjectedNRISHP.json").json()
// Imports and reads the polygons features from the topojson file.
Insert cell
legend = () => {
const k = 24;
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>`;
}
// Creates and formats the legend for a bivariate map, rotating it 45 degrees to allow for better understanding of the colors in relation to their values. This line also assigns the title of the legend (which is chosen in the "data" line).
Insert cell
color = {
return value => {
if (!value) return "#ccc";
let [a, b] = value;
return colors[y(b) + x(a) * n];
};
}
// Formats the colors for a bivariate map, given the color schemes chosen for visualization.
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)]})`}`;
}
// Formats the values and labels for a bivariate map.
Insert cell
topojson = require("topojson-client@3")
// Imports the topojson library
Insert cell
d3 = require("d3@5")
// Imports the d3 library.
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