Public
Edited
Oct 23, 2024
1 star
Insert cell
Insert cell
Insert cell
import { birthData } from "@zachpino/2d-heatmap"
Insert cell
//if we want to remove 2/29 from count visualizations we could do that like so...
birthDataWithoutFeb29 = birthData.filter(
(d) => d.month + " " + d.date !== "2 29"
)
Insert cell
Insert cell
//https://web.library.yale.edu/cataloging/months
monthLabels = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"June",
"July",
"Aug",
"Sept",
"Oct",
"Nov",
"Dec"
]
Insert cell
Insert cell
// evenly divide height (defined below) by number of months per year
rectHeight = (height - margin * 2) / 12
Insert cell
// evenly divide width by maximum numbers of days per month
rectWidth = (width - margin * 2) / 31
Insert cell
Insert cell
//month -> y
monthScale = d3
.scaleLinear()
.domain([0, 12]) //note that this is a bit weird...
.range([margin, height - margin])
Insert cell
//day -> x
dayScale = d3
.scaleLinear()
.domain([1, 32]) //because we're not starting at day 0, we need to go to 32!
.range([margin, width - margin])
Insert cell
//if we want to show total births by day
birthCountExtents = d3.extent(birthDataWithoutFeb29, (d) => d.births)
Insert cell
//if we wanted instead to show average births by day we could do this...
birthAverageExtents = d3.extent(birthData, (d) => d.births / d.count)
Insert cell
//convert births to number between 0 and 1 for use with color scales
birthParameterScale = d3.scaleLinear().domain(birthCountExtents).range([0, 1])
Insert cell
Insert cell
legendSteps = d3.range(legendBoxCount).map((d) => d / legendBoxCount)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof colorChoice = Inputs.select(["Greens", "Reds", "Blues"])
Insert cell
d3.interpolate("viridis")(0)
Insert cell
colorLUT = ({
Greens: d3.interpolateViridis,
Reds: d3.interpolateMagma,
Blues: d3.interpolateBlues
})
Insert cell
colorLUT[colorChoice](0.5)
Insert cell
viz = {
//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

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

//draw squares
for (let i = 0; i < birthData.length; i++) {
const bd = birthData[i];
svg
.append("rect")
.attr("width", rectWidth - internalMargin)
.attr("height", rectHeight - internalMargin)
.attr("x", dayScale(bd.date) + internalMargin / 2)
.attr("y", monthScale(bd.month - 1) + internalMargin / 2)
.attr("fill", d3.interpolateViridis(birthParameterScale(bd.births)));
}

//month labels
for (let i = 0; i < monthLabels.length; i++) {
const month = monthLabels[i];
svg
.append("text")
.text(month)
.attr("x", margin - labelOffset)
.attr("y", monthScale(i) + 0.5 * rectHeight)
.attr("fill", "white")
.attr("alignment-baseline", "middle")
.attr("text-anchor", "end")
.style("text-transform", "uppercase")
.attr("font-family", font)
.attr("font-size", labelFontSize);
}

//day labels
for (let i = 1; i < 32; i++) {
svg
.append("text")
.text(i)
.attr("x", dayScale(i) + rectWidth / 2)
.attr("y", margin - labelOffset)
.attr("fill", "white")
.attr("font-family", font)
.attr("font-size", labelFontSize)
.attr("text-anchor", "middle");
}

//add title
svg
.append("text")
.text(" Daily US Births, 1995-2014")
.attr("x", width - margin)
.attr("y", margin / 2)
.attr("fill", "white")
.attr("font-family", font)
.attr("text-anchor", "end");

//Legend
for (let i = 0; i < legendSteps.length; i++) {
svg
.append("rect")
.attr("width", legendSize)
.attr("height", legendSize)
.attr("x", i * legendSize + margin)
.attr("y", margin / 2 - legendSize / 4)
.attr("fill", d3.interpolateMagma(legendSteps[i]));

svg
.append("text")
.text(
birthParameterScale
.invert(legendSteps[i])
.toLocaleString(undefined, { maximumFractionDigits: 0 })
)
.attr("x", i * legendSize + margin + legendSize * 0.5)
.attr("y", margin / 2 - legendSize / 2)
.attr(
"transform",
`rotate(-45 ${i * legendSize + margin + legendSize * 0.5} ${
margin / 2 - legendSize / 2
})`
)
.attr("font-family", font)
.attr("font-size", labelFontSize)
.attr("fill", "white");
}
return svg.node();
}
Insert cell
Insert cell
Insert cell
simplifiedForLoopWithoutAnyObservableGlobalVariables = {
//svg variables
const height = 600;
const margin = 75;
//width comes from Observable

//calculate sizes
const rectHeight = (height - margin * 2) / 12;
const rectWidth = (width - margin * 2) / 31;

//month -> y
const monthToYScale = d3
.scaleLinear()
.domain([0, 12])
.range([margin, height - margin]);

//day -> x
const dayToXScale = d3
.scaleLinear()
.domain([1, 32]) //because we're not starting at 0, we need to go to 32!
.range([margin, width - margin]);

//extract high and low birthday averages
const birthAverageMinMax = d3.extent(birthData, (d) => d.births / d.count);

//birthAverages -> 0-1 for color scale use
const birthAveragesToZeroToOne = d3
.scaleLinear()
.domain(birthAverageMinMax)
.range([0, 1]);

//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#333344");

for (let i = 0; i < birthData.length; i++) {
//store each day in a reusable variable
const currentDay = birthData[i];

//make rectangles
svg
.append("rect")
.attr("width", rectWidth)
.attr("height", rectHeight)
.attr("x", dayToXScale(currentDay.date))
.attr("y", monthToYScale(currentDay.month - 1))
.attr(
"fill",
d3.interpolateMagma(
birthAveragesToZeroToOne(currentDay.births / currentDay.count)
)
);
}
return svg.node();
}
Insert cell
viz2 = {
//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

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

svg
.selectAll(".jdhfgsjfhg")
.data(birthDataWithoutFeb29)
.join("rect")
.attr("width", rectWidth - internalMargin)
.attr("height", rectHeight - internalMargin)
.attr("x", (bd) => dayScale(bd.date) + internalMargin / 2)
.attr("y", (bd) => monthScale(bd.month - 1) + internalMargin / 2)
.attr("fill", (bd) =>
d3.interpolateViridis(birthParameterScale(bd.births))
);

return svg.node();
}
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