Public
Edited
Mar 30
Insert cell
Insert cell
import { Scrubber } from "@mbostock/scrubber"
Insert cell
d3 = require("d3@7")
Insert cell
topojson = require("topojson-client@3")
Insert cell
data = (await FileAttachment("merged_data_date.csv").csv()).map((d) => {
d.date = new Date(d.Date);
d.launch_month = d.date.toISOString().slice(0, 7); // "YYYY-MM"
d.longitude = +d.longitude;
d.latitude = +d.latitude;
return d;
})
Insert cell
data[0]
Insert cell
months = Array.from(new Set(data.map((d) => d.launch_month)))
.filter(Boolean)
.sort()
Insert cell
width = 960
Insert cell
height = 500
Insert cell
projection = d3.geoMercator().fitSize([width, height], { type: "Sphere" })
Insert cell
path = d3.geoPath(projection)
Insert cell
colorScale = d3.scaleSequential(d3.interpolatePlasma).domain([0, 50])
Insert cell
trianglePath = "M 0 -10 L 5 10 L -5 10 Z"
Insert cell
world = await d3.json(
"https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"
)
Insert cell
countries = topojson.feature(world, world.objects.countries).features
Insert cell
viewof month = Scrubber(months, {
autoplay: true,
delay: 500,
loop: false,
format: (d) => `Month: ${d}`
})
Insert cell
svg = {
const svg = d3
.select(DOM.svg(width, height))
.attr("viewBox", [0, 0, width, height])
.style("display", "block")
.style("overflow", "visible")
.style("background", "#fff");

// Countries
svg
.selectAll("path.country")
.data(countries)
.join("path")
.attr("class", "country")
.attr("d", path)
.attr("fill", "#eee")
.attr("stroke", "#aaa");

window.svgSelection = svg; // expose for reuse

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

Insert cell
function updateLaunches(month) {
// Filter and clean the data
const filtered = data
.filter((d) => d.launch_month === month)
.filter(
(d) =>
d && typeof d.longitude === "number" && typeof d.latitude === "number"
);

const svg = d3.select("svg");

const triangles = svg
.selectAll("path.launch-triangle")
.data(filtered, (d) =>
d ? `${d.longitude}-${d.latitude}-${d.date}` : Math.random()
);

// Enter: new data
triangles
.enter()
.append("path")
.attr("class", "launch-triangle")
.attr("d", trianglePath)
.attr("fill", "lime")
.attr("stroke", "black")
.attr("opacity", 1)
.attr("transform", (d) => {
const coords = projection([d.longitude, d.latitude]);
if (!coords) return "translate(-9999,-9999)";
const [x, y] = coords;
return `translate(${x}, ${y}) scale(2)`;
})
.raise();

// Update: existing triangles
triangles.transition().duration(300).attr("opacity", 1);

// Exit: remove old triangles
triangles.exit().transition().duration(300).attr("opacity", 0).remove();
}
Insert cell
{
viewof month.addEventListener("input", (e) => updateLaunches(e.target.value));
updateLaunches(month); // initial
}
Insert cell
d3.selectAll(".launch-triangle").size()
Insert cell
data.filter((d) => d.launch_month === "2015-04")
Insert cell
Array.from(document.querySelectorAll(".launch-triangle")).map((el) =>
el.getBBox()
)
Insert cell
d3
.select(".launch-triangle")
.attr("fill", "lime")
.attr("stroke", "black")
.raise()
Insert cell
Array.from(document.querySelectorAll(".launch-triangle")).map((el) => {
const { x, y } = el.getBBox();
return { x, y };
})
Insert cell
{
const [x, y] = projection([-98.5795, 39.8283]); // Center of the US

d3.select("svg")
.append("path")
.attr("d", trianglePath)
.attr("class", "launch-triangle")
.attr("transform", `translate(${x}, ${y}) scale(2)`)
.attr("fill", "red")
.attr("stroke", "black")
.attr("opacity", 1);
}
Insert cell
<iframe width="100%" height="76" frameborder="0"
src="https://observablehq.com/embed/04ade9cd8fcace11@96?cells=view"></iframe>
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