Public
Edited
Dec 10, 2023
Insert cell
Insert cell
import {Plot} from "@observablehq/plot"
Insert cell
import {Inputs} from "@observablehq/inputs"
Insert cell
Insert cell
map = htl.html`<svg viewBox="0 0 975 610">
<g fill="none" stroke="#000" stroke-linejoin="round" stroke-linecap="round">
<path stroke="#aaa" stroke-width="0.5" d="${path(counties)}"></path>
<path stroke-width="0.5" d="${path(states)}"></path>
<path d="${path(nation)}"></path>
</g>
</svg>`

Insert cell
path = d3.geoPath()
Insert cell
states = topojson.mesh(us, us.objects.states, (a, b) => a !== b)
Insert cell
nation = topojson.feature(us, us.objects.nation)
Insert cell
counties = topojson.mesh(us, us.objects.counties, (a, b) => a !== b && (a.id / 1000 | 0) === (b.id / 1000 | 0))
Insert cell
us = FileAttachment("counties-albers-10m.json").json() // Already projected to Albers-USA!
Insert cell
d3 = require("d3@7")
Insert cell
airports_jsonData = d3.json("https://raw.githubusercontent.com/e9d8v/cs448b/main/airports.json").then(function(airports_jsonData) {
return airports_jsonData;
});
Insert cell
delays_jsonData = d3.json("https://raw.githubusercontent.com/e9d8v/cs448b/main/airports_delays.json").then(function(delays_jsonData) {
return delays_jsonData;
});
Insert cell
routes_jsonData = d3.json("https://raw.githubusercontent.com/e9d8v/cs448b/main/routes.json").then(function(routes_jsonData) {
return routes_jsonData;
});
Insert cell
airportsData = airports_jsonData
Insert cell
delayData = delays_jsonData
Insert cell
routesData = routes_jsonData
Insert cell
delayData.forEach(delay => {
if (airportsData[delay.origin_airport]) {
airportsData[delay.origin_airport].delay_rate = delay.delay_rate;
airportsData[delay.origin_airport].flights_count = delay.flights_count;
}
});
Insert cell
projection = d3.geoAlbersUsa().scale(1300).translate([487.5, 305]);
Insert cell
projectedPoints = Object.values(airportsData).map(d => {
const projectedPoint = projection([+d.longitude, +d.latitude]);
if (!projectedPoint) return null;

const [x, y] = projectedPoint;
return {
x, y,
'IataCode': d.iata_code,
'Airport': d.airport,
'DelayRate': d.delay_rate,
'FlightsCount': d.flights_count
};
}).filter(d => d);
Insert cell
radiusScale = d3.scaleSqrt()
.domain(d3.extent(projectedPoints, d => d.DelayRate))
.range([2, 20]); // Min and max radius of circles
Insert cell
projectedRoutes = routesData.map(route => {
const origin = airportsData[route.origin_airport];
const destination = airportsData[route.destination_airport];

if (origin && destination) {
const originCoords = projection([+origin.longitude, +origin.latitude]);
const destCoords = projection([+destination.longitude, +destination.latitude]);

if (originCoords && destCoords) {
return {
origin: originCoords,
destination: destCoords,
count: route.count
};
}
}
return null;
}).filter(route => route);
Insert cell
function displayFlightPaths(airportCode, routes) {
svg.selectAll("line.flight-path").remove();

// Filter routes to get only those originating from the clicked airport
const airportRoutes = routes.filter(r => airportsData[r.origin_airport].iata_code === airportCode);

svg.selectAll("line.flight-path")
.data(airportRoutes)
.enter().append("line")
.attr("class", "flight-path")
.attr("x1", d => d.origin[0])
.attr("y1", d => d.origin[1])
.attr("x2", d => d.destination[0])
.attr("y2", d => d.destination[1])
.attr("stroke", "blue")
.attr("stroke-width", 2)
.on("mouseover", (event, d) => {
tooltip.style("opacity", 1)
.html(`Route: ${d.origin_airport} to ${d.destination_airport}<br>Flights Count: ${d.count}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", () => {
tooltip.style("opacity", 0);
});
}
Insert cell
tooltipDiv = d3.create("div")
.style("position", "absolute")
.style("opacity", 0)
.style("background", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px")
.style("pointer-events", "none")
.style("display", "none")
.node();

Insert cell
Insert cell
{
const svg = d3.select(map);

svg.selectAll("circle.airport")
.data(projectedPoints)
.join("circle")
.attr("class", "airport")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => radiusScale(d.DelayRate))
.attr("fill", "red")
.attr("fill-opacity", 0.6)
//.call(tooltip);

return svg.node();
}

Insert cell
Insert cell
d3.csv('https://raw.githubusercontent.com/fbottazzini/448B/main/Flight%20Delays%202015/airlines.csv').then(data => {
console.log(data);
});
Insert cell
airlinesData = d3.csv("https://raw.githubusercontent.com/e9d8v/cs448b/main/airlines.csv", function(d) {
return {
// We parse the data into an array of csv objects. We could for example change the names of fields.
Code: d.IATA_CODE,
Airline: d.AIRLINE
};
}).then(function(airlinesData) {
//Outside of Observable notebooks you would put all code to draw the graph here.
return airlinesData;
});
Insert cell
flightsData = d3.csv("https://raw.githubusercontent.com/e9d8v/cs448b/main/flights.csv", function(d) {
return {
Month: d.MONTH,
Day: d.DAY,
Airline: d.AIRLINE,
FlightNum: d.FLIGHT_NUMBER,
OriginAirport: d.ORIGIN_AIRPORT,
DestAirport: d.DESTINATION_AIRPORT,
DepartureDelay: d.DEPARTURE_DELAY,
ScheduledTime: d.SCHEDULED_TIME,
ElapsedTime: d.ELAPSED_TIME,
AirTime: d.AIR_TIME,
Distance: d.DISTANCE,
ScheduledArrival: d.SCHEDULED_ARRIVAL,
ArrivalTime: d.ARRIVAL_TIME,
ArrivalDelay: d.ARRIVAL_DELAY,
Diverted: d.DIVERTED,
Cancelled: d.CANCELLED,
CancellationReason: d.CANCELLATION_REASON,
AirSystemDelay: d.AIR_SYSTEM_DELAY,
SecurityDelay: d.SECURITY_DELAY,
AirlineDelay: d.AIRLINE_DELAY,
LateAircraftDelay: d.LATE_AIRCRAFT_DELAY,
WeatherDelay: d.WEATHER_DELAY
};
}).then(function(flightsData) {
//Outside of Observable notebooks you would put all code to draw the graph here.
return d3.rollup(flightsData,
v => ({
totalDelays: d3.sum(v, d => d.DepartureDelay > 0 ? 1 : 0),
totalCancellations: d3.sum(v, d => d.Cancelled),
}),
d => d.AIRLINE);
});

Insert cell
width = 2000
Insert cell
height = 200
Insert cell
flightsData
Insert cell
jsonData = d3.json("https://raw.githubusercontent.com/e9d8v/cs448b/main/months.json").then(function(jsonData) {
// Assuming jsonData is already in the correct format
return jsonData;
});
Insert cell
chart = {
const monthData = jsonData;
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

const width = 928;
const height = 500;
const marginTop = 30;
const marginRight = 0;
const marginBottom = 30;
const marginLeft = 40;

const x = d3.scaleBand()
.domain(monthData.map(d => monthNames[d.month - 1]))
.range([marginLeft, width - marginRight])
.padding(0.1);

const y = d3.scaleLinear()
.domain([0, d3.max(monthData, d => d.count)])
.range([height - marginBottom, marginTop]);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

svg.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(monthData)
.join("rect")
.attr("x", d => x(monthNames[d.month - 1]))
.attr("width", x.bandwidth())
.attr("y", d => y(d.count))
.attr("height", d => y(0) - y(d.count));

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0));

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("Total Delays"));

return svg.node();
}

Insert cell
aggregatedDelays = d3.rollup(flightsData,
v => ({
totalDelaysCancellations: d3.sum(v, d => (d.DepartureDelay > 0 ? 1 : 0) + d.Cancelled)
}),
d => d.OriginAirport);
Insert cell
airportsData.forEach(airport => {
const delays = aggregatedDelays.get(airport.Code);
airport.TotalDelaysCancellations = delays ? delays.totalDelaysCancellations : 0;
});
Insert cell
// Ensure airportsData includes TotalDelaysCancellations and coordinates are numeric
airportsData.forEach(airport => {
airport.Latitude = +airport.Latitude;
airport.Longitude = +airport.Longitude;
// Assume TotalDelaysCancellations is already calculated and merged
});

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