Public
Edited
Apr 11
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
//This is how you position the legend so it does not interfere with the map
svg.append(legend)
.attr("transform", "translate(670,250)");
//this is how you bring the data together to display values when hovered over each polygon
svg.append("g")
.selectAll("path")
.data(topojson.feature(polygons, polygons.objects.tl_2020_06_tract_LACounty).features)
.join("path")
.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
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/
//below are the color schemes for the bivariate map. the colors must be good for both single variables and multiplied together to create the bivariate map color scheme. With the HEX codes, you line them upper from lower left to the 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 creates the number of classes we are using for the bivariate map and legend
n = Math.floor(Math.sqrt(colors.length))
Insert cell
//This creates the classification for the first variable
x = d3.scaleQuantile(Array.from(data.values(), d => d[0]), d3.range(n))
Insert cell
//This creates the classification of the second variable
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
//this part adds data to the basemap
polygon_features = topojson.feature(polygons, polygons.objects.tl_2020_06_tract_LACounty)
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("losang_unemployment_disabilities.csv").text(), ({Name, disabled_pct, Percent_Unemployed}) => [Name, [+disabled_pct, +Percent_Unemployed]])), {title: ["% with Disabilities", " % Unemployed"]})
Insert cell
//this is the common attribute betweeen the csv and topojson files
idAttribute = "NAME"
Insert cell
//this adds the topojson to create the polygon basemap
polygons = FileAttachment("tl_2020_06_tract_LACounty.json").json()
Insert cell
height = 610
Insert cell
width = 875
Insert cell
//This creates the rotated legend for the bivariate map. The transform=translate code line is the angle/ rotation of the legend
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 creates the color of the cell if it is undefined
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
//If one or both values in the polygon is undefined, the output of the bivariate map will be "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
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