Public
Edited
Oct 17, 2022
Fork of City Racers
Insert cell
Insert cell
Insert cell
rawCsaData = await d3.json(
"https://api.census.gov/data/2021/acs/acs1?get=NAME,B01001_001E&for=combined%20statistical%20area:*"
)
Insert cell
Insert cell
cleanCsaData = rawCsaData.slice(1).map((csa) => {
//.slice(1) cuts off the zeroeth item of the array to get rid of the header row
const cityObject = {
name: csa[0],
//split at "-" and grab whatever is before the first "-"
shortName: csa[0].split("-")[0],
//we start with this format "Minneapolis-St. Paul, MN-WI CSA"
//we want everything between the ", " and " CSA", split at hyphens
//grab the name of the CSA at position 0...
//split at a command and a space (to prevent splitting if CSAs have spaces in their name)...
//grab whatever is after the first comma [1]...
//replace " CSA" with nothing ""...
//split the result at "-" to individual state abbreviations
states: csa[0].split(", ")[1].replace(" CSA", "").split("-"),
population: parseInt(csa[1]),
csaID: parseInt(csa[2])
};
return cityObject;
})
Insert cell
//to see a single example of the fancy text splitting above
rawCsaData[19][0].split(", ")[1].replace(" CSA", "").split("-")
Insert cell
Insert cell
Insert cell
Insert cell
csaData = cleanCsaData.filter((csa) => csa.states.includes(selectedState))
Insert cell
Insert cell
Insert cell
// Names -> Pixels
yScale = d3
.scalePoint()
.domain(shortNames)
.range([yMargin, height - yMargin])
Insert cell
//using D3 axis instead of direct placement with .append(), either is fine.
yAxis = d3
.axisLeft(yScale)
.tickPadding(10)
.tickSizeInner(-width + xMargin * 2)
.tickSizeOuter(-width + xMargin * 2)
Insert cell
//Population -> Speed
speedScale = d3
.scaleLinear()
.domain(d3.extent(csaData, (d) => d.population))
.range([fastestTime, slowestTime])
Insert cell
//Population -> Number between 0 and 1 for color scale use
colorScale = d3.scaleLinear().domain(d3.extent(csaData, (d) => d.population))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
cityRacers = {
//artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//background
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", bgColor);

//finish line
svg
.append("rect")
.attr("x", width - xMargin)
.attr("y", yMargin)
.attr("width", 8)
.attr("height", height - yMargin * 2)
.attr("fill", "white");

//Make a group ('g') in the SVG
svg
.append("g")
.attr("transform", "translate(" + xMargin + ",0)")
.call(yAxis);

//after the axis is drawn, we can change other visual styles
svg
.selectAll(".tick text") //find all the tick labels
.attr("font-size", "11px") //change font size
.attr("fill", "white") //change font color
.attr("font-family", "menlo"); //change font

//inner tick marks styles
svg.selectAll("g line").attr("stroke", "white").attr("stroke-width", 2);

//axis line and outer tick styles
svg.select("g path").attr("stroke", "white").attr("stroke-width", 2);

//data binding to create each circle
svg
.selectAll(".csaDots")
.data(csaData)
.enter()
.append("circle")
.attr("cx", xMargin)
.attr("cy", (d) => yScale(d.shortName))
// .attr("cy", (d, i) => yScaleIndex(i))
.attr("r", 5)
.attr("fill", "white")
.attr("class", "csaDots");

//create svg button and attach event listener for moving dots
svg
.append("rect")
.attr("fill", "orange")
.attr("width", 50)
.attr("height", 20)
.attr("x", width - 60)
.attr("y", 10)
.on("click", () => {
d3.selectAll(".csaDots")
.transition()
.duration((d) => speedScale(d.population))
.ease(d3.easeBounce)
.attr("cx", width - xMargin)
.attr("fill", (d) => d3.interpolateMagma(colorScale(d.population)));
});

//label for the button
svg
.append("text")
.attr("fill", "white")
.attr("x", width - 58)
.attr("y", 22)
.text("Go!")
.style("font-family", "courier")
.style("font-size", 14)
//so the text doesn't get in the way of our button seeing the mouse click...
.attr("pointer-events", "none");

//create svg button and attach event listener for resetting dots
svg
.append("rect")
.attr("fill", "orange")
.attr("cursor", "crosshair")
.attr("width", 50)
.attr("height", 20)
.attr("x", width - 60)
.attr("y", 40)
.on("click", () => {
d3.selectAll(".csaDots")
.transition()
.duration((d) => speedScale(d.population))
.ease(d3.easeBounce)
.attr("cx", xMargin)
.attr("fill", "white");
});

//label for the button
svg
.append("text")
.attr("fill", "white")
.attr("x", width - 58)
.attr("y", 54)
.text("Reset")
.style("font-family", "courier")
.style("font-size", 14)
//so the text doesn't get in the way of our button seeing the mouse click...

.attr("pointer-events", "none");

return svg.node();
}
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

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