Public
Edited
Apr 18, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg") //Creates my svg "canvas" for my page
.attr("viewBox", [0, 0, width, height]); // The width and height of the page is defined below as variables. 0,0 refers to margin size, here no padded margins. Depending upon the state and if there needs to be room for the legend, the width and height can be changed below.

svg.append(Legend) // Creates a legend as the specifications were made in below code called "legend".
.attr("transform", "translate(85,400)"); // placement of legend on the canvas (in this case, left middle of canvas). Change the "translate (#,#)" to move it around the canvas.
//NULL Value Legend
svg.append("g")
.attr("transform", "translate(60,490)")
.append(() =>
legend({
color: nullcolorforlegend,
title: "No Data",
width: 50,
tickFormat: ".1f"
})
);

svg.append("g") // adds basemap of zipcodes.
.selectAll("path")
.data(topojson.feature(polygons, polygons.objects.count_prov_and_female).features) // This is the data for my basemap of zipcodes called polygons. The object within that is called count_prov_and_female.
.join("path")
.attr("stroke", "#525252") // This is the color of the outline for the zipcodes. the ".attr" allows you to manipulate a graphic's attributes
.attr("stroke-width", 0.5) // This is the thickness of the polygon (zipcode) outlines.
.attr("fill", d => color(data.get(d.properties[idAttribute]))) // This links the color of the polygon fill by zipcode to the values of the two attributes specified in the dictionary we created below called "data".
.attr("d", path) // draws lines between polygons (zipcodes) according to projection
.append("title")
.text(d => `${d.properties[idAttribute]}, ${format(data.get(d.properties[idAttribute]))}`); // When the user hovers over the zipcode, the ID of the zipcode, the provider rate per 10,000 persons and the %female providers will be displayed.

return svg.node();
}
Insert cell
// http://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/
schemes = [
{
name: "RdBu", // all nine of the colors and location within the legend that will make up the Red and Blue bivariate color scheme.
colors: [
"#e8e8e8", "#e4acac", "#c85a5a",
"#b0d5df", "#ad9ea5", "#985356",
"#64acbe", "#627f8c", "#574249"
]
},
{
name: "BuPu", // all nine of the colors and location within the legend that will make up the Blue and Purple bivariate color scheme.
colors: [
"#e8e8e8", "#ace4e4", "#5ac8c8",
"#dfb0d6", "#a5add3", "#5698b9",
"#be64ac", "#8c62aa", "#3b4994"
]
},
{
name: "GnBu", // all nine of the colors and location within the legend that will make up the Green and Blue bivariate color scheme.
colors: [
"#e8e8e8", "#b5c0da", "#6c83b5",
"#b8d6be", "#90b2b3", "#567994",
"#73ae80", "#5a9178", "#2a5a5b"
]
},
{
name: "PuOr", // all nine of the colors and location within the legend that will make up the Purple and Orange bivariate color scheme.
colors: [
"#e8e8e8", "#e4d9ac", "#c8b35a",
"#cbb8d7", "#c8ada0", "#af8e53",
"#9972af", "#976b82", "#804d36"
]
}
]
Insert cell
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 UTM projection.
projection = d3.geoTransverseMercator().rotate([94,0]).fitExtent([[80, 80], [width, height]], polygon_features);
Insert cell
polygon_features = topojson.feature(polygons, polygons.objects.count_prov_and_female) // get
Insert cell
data = Object.assign(new Map(d3.csvParse(await FileAttachment("count_prov_and_female.csv").text(), ({ZIP_CODE, Count_zip, female_pro, POPULATION}) => [ZIP_CODE, [+Count_zip/POPULATION*1000, 100*+female_pro/+Count_zip]])), {title: ["Provider/10000", "% Female Providers"]})
//makes an object out of the values in the array [] in the csv objects(count_prov_and_female) line. Coordinates name of park to # of visitors
//"await" makes loading the data in the csv asynchronous so the whole thing does not have to come at once and it all crash
//[] are the array where values will be

// Provider count was normalized using population size from the census data and then multiplied per 10,000 persons so that I got whole numbers to display. Percent Female Providers was standardized by the number of providers in each zip code.
// A title was assigned to each new variable/ array that was created. This title will display both in the legend axes and when the user hovers over a zipcode.
Insert cell
idAttribute = "ZIP_CODE" // will call ZIP_CODE as the id attribute - just to make things easier
Insert cell
polygons = FileAttachment("count_prov_and_female_zip_23.json").json() //import the polygon base map data of zipcodes
Insert cell
height = 600 // height of map canvas
Insert cell
width = 975 // width of map canvas
Insert cell
Legend = () => { // DO NOT change if working with a new bivariate map. This creates the legend according to the values from the arrays above and displays the legend at a 45degree angle for better user understanding of high/high and low/low values.
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 = { // no need to change - this displays the colors of the polygons according to the color schemes created and then chosen in the drop down menu at the top of the map.
return value => {
if (!value) return "#ccc";
let [a, b] = value;
return colors[y(b) + x(a) * n];
};
}
Insert cell
formatNum = d3.format(".1f") // makes number have 1 significant figure
Insert cell
format = (value) => { // No need to change - this formats the values of the attributes and what is displayed when the user hovers over the polygons. If I add ${formatNum(a)} before the labels for a, it will display the values as 1 significant figures. Same is true for ${formatNum(b)}.
if (!value) return "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)]})`}`;
}
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@5") // brings in d3 package
Insert cell
Insert cell
//This makes every null (N/A) value colored grey in the legend.
nullcolorforlegend = d3.scaleOrdinal()
.domain(["0"])
.range(["black"])
Insert cell
count_prov_and_female.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

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