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

svg.append(legend)
//creates a legend
.attr("transform", "translate(700,600)");
//applies a translation transformation, with the first number representing the translation to the right and the second the translation down from the top left (0,0) origin
svg.append("g")
.selectAll("path")
.data(topojson.feature(polygons, polygons.objects.DenverCOTracts).features)
//joins the geometries of the gejson to the attributes of the csv for spatial display
.join("path")
.attr("fill", d => color(data.get(d.properties[idAttribute])))
.attr("d", path)
.append("title")
.text(d => `${format(data.get(d.properties[idAttribute]))}`);
//joins the geometries of the gejson to the attributes of the csv to be displayed when hovered over

return svg.node();
}
Insert cell
md`## Interpretation
Overall, the proportion of 65+ residents and percentage of homes occupied by the owner without a house mortgage strongly correlated. We can tell this because the polygons of matching categories (low, medium, or high in both categories) dominated the map. There were very few opposing extremes, with only two tracts having a low 65+ level and high owned no mortgage level, and 6 tracts having a high 65+ level and low owned no mortgage level. 65+ residents generally seemed to live towards the edges of the city, with concentrations in the east and southwest. Residence ownership without a mortgage appears to be lowest toward the center of the city, as well as the northeastern corner. The population under 65 generally follows this spatial distribution as well.
`
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/

//Defines different color schemes which can be applied to the map. Choose two sequential color schemes with three steps, the first of each being a very light, neutral color. The middle colors should be the same hue as the last colors, but with a lower saturation and higher brightness. Generally, choose two colors that are close to complementary (Stevens).

//New schemes can be added by defining a new name in the style below, then defining the array "colors", and filling it with the 9 colors of the bivariate 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"
]
}
]
Insert cell
nullcolorforlegend = d3.scaleOrdinal()
.domain([NaN])
.range([nancolor])
Insert cell
nancolor = d3.color("grey")
Insert cell
labels = ["low", "", "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 Conic Conformal projection.
projection = d3.geoConicConformal()
.parallels([38 + 27 / 60, 39 + 45 / 60])
.rotate([105 + 30 / 60, 0]).fitExtent([[80, 80], [width, height]], polygon_features);
Insert cell
polygon_features = topojson.feature(polygons, polygons.objects.DenverCOTracts)
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("census_tracts_2010.csv").text(), ({GEOID_NUM, PCT_65_PLUS, HOUSING_UNITS, OWNED_FREE_CLEAR}) => [+GEOID_NUM, [+PCT_65_PLUS, 100*+OWNED_FREE_CLEAR/+HOUSING_UNITS]])), {title: ["% 65 Plus", "% Owned No Mortgage"]})
Insert cell
idAttribute = "GEOID_NUM"
Insert cell
polygons = FileAttachment("DenverCOTracts.json").json()
Insert cell
height = 800
Insert cell
width = 1000
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>`;
}
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";
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