Published
Edited
Dec 1, 2020
3 stars
Insert cell
Insert cell
// Keep track of the section so that updates only happen when the section changes
mutable currentSection = 0
Insert cell
chart = {
// Overall container
const container = d3.create("div").attr("class", "scroll-container");

// Container for the narrative section
const narrativeContainer = container
.append("div")
.attr("class", "narrative-container");

// Narrative section (holds the text on the left)
const narrative = narrativeContainer.append("div").attr("class", "narrative");

// Add text
narrative
.selectAll(".section")
.data(descriptions)
.join("div")
.attr("class", "section")
.append("text")
.text(d => d);

// Visualization container
const vis = container.append("div");

const svg = vis
.append("svg")
.attr("height", height)
.attr("width", chartWidth);

// Add the axes
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);

// Update function (just switches the colors)
const update = section => {
// If the section hasn't changed, don't do anything!
if (section === mutable currentSection) return;
mutable currentSection = section;

// Filter down based on month
const min_date = x.domain()[0];
const max_date = new Date(min_date.setMonth(min_date.getMonth() + section));

// fancy transition

const transition = ele =>
ele
.transition()
.duration(1000)
.delay((d, i) => {
// Calculate days betwen this date and the max date
const one_day = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
const days_between = (max_date.getTime() - d.x.getTime()) / one_day;
return 50 * (30 - days_between);
})
.attr("r", 3);
const points = svg
.selectAll("circle")
.data(ny_data.filter(d => d.x < max_date))
.join(enter =>
enter
.append("circle")
.attr("r", 0)
.call(transition)
)
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y));
};

// Add the scrolling listener
narrativeContainer.on("scroll", () => {
// Compute section
const totalHeight = narrativeContainer.node().getBoundingClientRect().top;
const offset = narrative.node().getBoundingClientRect().y;
const section = Math.floor((totalHeight - offset + 10) / sectionHeight);
update(section);
});

return container.node();
}
Insert cell
md`## Scales and Axes`
Insert cell
x = d3
.scaleTime()
.domain(d3.extent(ny_data.map(d => d.x)))
.range([margin.left, chartWidth - margin.right])
Insert cell
y = d3
.scaleLinear()
.domain(d3.extent(ny_data, d => d.y))
.range([height - margin.bottom, margin.top])
Insert cell
xAxis = g =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
Insert cell
yAxis = g =>
g.attr("transform", `translate(${margin.left},0)`).call(d3.axisLeft(y))
Insert cell
settings = md`## Settings`
Insert cell
// Descriptions for each section
descriptions = [
"COVID in New York",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November"
]
Insert cell
height = 400
Insert cell
sectionHeight = 200
Insert cell
narrativeWidth = 200
Insert cell
margin = ({ left: 100, right: 50, top: 50, bottom: 100 })
Insert cell
chartWidth = width - narrativeWidth - 20
Insert cell
Insert cell
import { formatted_data } from "@mkfreeman/recreating-nyt-u-s-cases-map"
Insert cell
ny_data = formatted_data
.filter(d => d.state_full === "New York")[0]
.data.map(d => ({ x: new Date(d.key), y: d.value }))
Insert cell
import { drawChart, chart_data } from "@info474/d3-small-multiples"
Insert cell
html`<style>

.scroll-container {
width:${width}px;
height:${height}px;
}

.narrative-container {
height:${height}px;
overflow-y:scroll;
float:left;
}

.narrative {
width:${narrativeWidth}px;
}

.vis-container {
height:${height}px;
width:${width - narrativeWidth - 20}px;
float:right;
}

.section {
height: ${sectionHeight}px;
}
.section:last-of-type {
height:${height}px;
}
</style>`
Insert cell
d3 = require("d3")
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