Public
Edited
Apr 6, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//NULL VALUE INFORMATION
import {legend} from "@d3/color-legend"
Insert cell
//This makes every null (N/A) value colored grey in the legend.
nullcolorforlegend = d3.scaleOrdinal()
.domain(["N/A"])
.range(["grey"])
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/

//These are the color schemes for each bivariate mapping. The colors must be a useful gradient as single variables AND be multiplied together to create the bivariate variables. After you have found the HEX codes, you line them up from the lower left to upper right section of the legend.
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
labels = ["low", "", "high"]
Insert cell
//This defines the amount of classes we are using in the bivariate legend
n = Math.floor(Math.sqrt(colors.length))
Insert cell
//This creates a classification for the first variable we are using
x = d3.scaleQuantile(Array.from(data.values(), d => d[0]), d3.range(n))
Insert cell
//This creates a classification for the second varibale we are using
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([85,0]).fitExtent([[10, 10], [width, height]], polygon_features);
Insert cell
//This adds data to the topological data we added to the map earlier. Example: The GEOID values
Insert cell

polygon_features = topojson.feature(polygons, polygons.objects.lansing_tracts_topo)
Insert cell
data.get(26045020305)
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("MichiganData@1.csv").text(), ({GEOID, PovertyRate, TractBlack, Pop2010}) => [GEOID, [+PovertyRate, +TractBlack/+Pop2010 * 100]])), {title: ["% Poverty Rate", "% Black"]})
Insert cell
//This is the attribute that is shared within the topojson and csv file
idAttribute = "GEOID"
Insert cell
//This adds the topojson that that creates the polygons on the map.
polygons = FileAttachment("lansing_tracts_topo.json").json()
Insert cell
height = 550
Insert cell
width = 850
Insert cell
//This creates the rotated bivariate legend layout. It rotates the image, adds the lines on each of the axis, adds the two ranges and fills them with the colors that are found in the schemes
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>`;
}
Insert cell
//This defines the color of the cell if the data is undefined
color = {
return value => {
if (!value) return "grey";
let [a, b] = value;
return colors[y(b) + x(a) * n];
};
}
Insert cell
formatNum = d3.format(".1f")
Insert cell
//If either value of the polygon is undefined, the output of the bivariate map is undefined or in this case "N/A"
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

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