Published
Edited
Dec 16, 2021
2 forks
Importers
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// CLICK THE CARET ABOVE TO VIEW A PREVIEW OF THE DATA
rocketLaunchesRaw = {
// const url = "https://ll.thespacedevs.com/2.2.0/launch/?format=json"
const url = 'https://fdo.rocketlaunch.live/json/launches/next/5'
return d3.json(url) // We separate the URL from our code to help keep things readable.
}
Insert cell
Insert cell
dateParse = d3.timeParse("%Y-%m-%dT%H:%M:%S%Z") // Parse = turn "text" into a "date". If this is wrong
// the date values below will parse to "null".
Insert cell
dateFormat = d3.timeFormat("%Y-%m-%d") // Format is the reverse: Convert a date into a readable string.
Insert cell
Insert cell
rocketLaunchesTable = {
let cleaned = rocketLaunchesRaw.result
return cleaned.map((row)=> {
// Let's pick out a subset of the interesting properties in this dataset for use later on.
return {
name: row.name,
// net: dateParse(row.net), // Convert ISO timestamp to Javascript Dates
net: new Date(+row.sort_date * 1000),
location: row.pad.location,
// probability: +row.probability, // Convert string to number
rocket: row.vehicle,
lsp: row.provider,
// missions: row.missions,
// num_agencies: row.rocket.agencies?.length || 0 // This is our calculated property!
num_missions: row.missions?.length || 0
}
})
}
Insert cell
Insert cell
Insert cell
margin = ({top: 20, right: 0, bottom: 30, left: 40}) // This is a d3 convention for chart spacing.
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// This is our scaling function for the x-axis (horizontal)
xScale = d3.scaleBand()
.domain(rocketLaunchesTable.map(d => d.name))
.range(xRange)
.padding(0.15) // how much space to put around each bar
Insert cell
Insert cell
// We can use the helper function d3.extent to get the maximum and minimum values in a list of numbers.
num_missions_range = [0 , max_launch_count]
Insert cell
Insert cell
// This is our scaling function for the y-axis (vertical)
yScale = d3.scaleLinear()
.domain(num_missions_range) // tracks max and min automatically
.range(yRange)
.nice()
Insert cell
Insert cell
chart = {
const svg = d3.select(DOM.svg(width, height)); // This is just an Observable pattern. Think of it as "home".
svg.append("g")
.selectAll("rect") // In d3, we select all elements even if they don't exist yet
.data(rocketLaunchesTable) // This is where our data actually gets added! In future, add key.
.enter() // Now we handle all elements that are actually new...
.append("rect") // For each new data element, we create a "rect" element.
.attr("x", d => xScale(d.name)) // The x position, aided by our key function
.attr("y", d => yScale(d.num_missions)) // This is the starting y coordinate for
.attr("height", d => yScale(0) - yScale(d.num_missions))
.attr("width", xScale.bandwidth() / 1) // feel free to change the number 1 to rescale the bars.
.attr("fill", "#78CDD7")
return svg.node();
}
Insert cell
Insert cell
xAxis = g => g // This is a FUNCTION because it's something that we need to call.
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale)
// .ticks(width / 80) // Magic number
// .tickSizeOuter(0)
)
.call(g => g.select(".domain").remove()) // Tufte style: remove the axis line
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale) // place tickets to the left of the bar
.ticks(max_launch_count)
.tickSize(0)
.tickFormat(d3.format("d"))
)
.call(g => g.select(".domain").remove()) // Tufte style: remove the axis line
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
bar_color = function(row) {
if (row.location.country === "United States") {
return "#78CDD7"; // pale turqoise saved from earlier
} else {
return "lightgrey";
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
rocketLaunchesNested = d3.nest()
.key(d => d.net) // Group objects by date
.rollup(group => group.length) // Function to apply to each group
.entries(rocketLaunchesTable)
.map(d => {
return {
date : d3.isoParse(d.key),
launches: d.value
}
})
Insert cell
Insert cell
// We need to get the first and last day in our date range
dayExtent = {
let range = d3.extent(rocketLaunchesNested.map(d=> d.date))
// We need the last day to be offset by 1 so that the last day is included
range[1] = d3.timeDay.offset(range[1], 1)
return range
}
Insert cell
// Next, we want a list of all the days in between that date range
dayRange = d3.timeDays(...dayExtent)
Insert cell
xScaleNested = d3.scaleTime()
.domain(dayExtent)
.range(xRange)

Insert cell
yScaleNested = d3.scaleLinear()
.domain([0, d3.max(rocketLaunchesNested.map(d => d.launches))])
.range(yRange)
Insert cell
yAxisNested = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisRight(yScaleNested) // place tickets to the left of the bar
.ticks(d3.max(rocketLaunchesNested.map(d => d.launches)))
.tickSize(width)
)
.call(g => g.select(".domain").remove()) // Tufte style: remove the axis line
Insert cell
xAxisNested = g => g // This is a FUNCTION because it's something that we need to call.
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScaleNested)
.ticks(d3.timeWeek, 1)
.tickSize(0)
.tickPadding(10)
.tickFormat(d3.timeFormat("%m/%d"))
)
.call(g => g.select(".domain").remove())
Insert cell
Insert cell
Insert cell
Insert cell
html`
<style>
.axisWhite line {
stroke: white;
stroke-width: 2px;
}
</style>
`
Insert cell
Insert cell
Insert cell
us = d3.json("https://unpkg.com/us-atlas@1/us/10m.json")
Insert cell
topojson = require("topojson-client@3")
Insert cell
// Only keep the launches from America
americanLaunches = rocketLaunchesTable.filter(d => d.location.country === "United States Retired")
.map(d => {
let location = d.location.pads[0]
return {
coords: [+location.longitude, +location.latitude],
location: d.location,
name: d.name
}
})
Insert cell
Insert cell
Insert cell
Insert cell
map = {
const width = 960;
const height = 600;
const projection = d3.geoAlbersUsa()
.translate([width/2, height/2])
.scale(SCALE_FACTOR);
const path = d3.geoPath();
const formatNumber = d3.format(",.0f");
const svg = d3.select(DOM.svg(width, height))
.style("width", "100%")
.style("height", "auto");

// Draw land
// docs on topojson: https://github.com/topojson/us-atlas
svg.append("path")
.datum(topojson.feature(us, us.objects.nation))
.attr("fill", "#ddd")
.attr("d", path);

// Draw state boundaries
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path);

// Draw circles on the land
svg.append("g")
.attr("fill", "brown")
.attr("fill-opacity", 0.5)
.attr("stroke", "#fff")
.attr("stroke-width", 0.5)
.selectAll("circle")
.data(americanLaunches)
.enter().append("circle")
.attr('cx', d => projection(d.coords)[0])
.attr('cy', d => projection(d.coords)[1])
.attr("r", "3")
.append("title")
.text(d => `${d.name} | ${d.location.name})`)

return svg.node();
}
Insert cell
Insert cell
// Import Leaflet
L = require('leaflet@1.2.0')
Insert cell
// Add Leaflet CSS
html`<link href='${resolve('leaflet@1.2.0/dist/leaflet.css')}' rel='stylesheet' />`
Insert cell
onEachFeature = function (feature, layer) {
// does this feature have a property named popupContent?
if (feature.properties && feature.properties.popupContent) {
layer.bindPopup(feature.properties.popupContent);
}
}
Insert cell
leaflet_map = {

let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;
// Now we create a map object and add a layer to it.
let map = L.map(container).setView([35, -100.9923], 3);
let osmLayer = L.tileLayer('https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}@2x.png', {
attribution: 'Wikimedia maps beta | &copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// Now let's add some points!
let geoJsonFeatures = americanLaunches.map(d => {
return {
type: "Feature",
properties: {
popupContent: `${d.location.name} || ${d.name}`
},
geometry: {
type: "Point",
coordinates: d.coords
}
}
})
L.geoJSON(geoJsonFeatures, {
onEachFeature: onEachFeature
}).addTo(map);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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