Public
Edited
Dec 11, 2023
Fork of D3 U.S. map
2 forks
Insert cell
Insert cell
import {Plot} from "@observablehq/plot"
Insert cell
import {Inputs} from "@observablehq/inputs"
Insert cell
Insert cell
Insert cell
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) {
// Assuming jsonData is already in the correct format
return airports_jsonData;
});
Insert cell
delays_jsonData = d3.json("https://raw.githubusercontent.com/e9d8v/cs448b/main/airports_delays.json").then(function(delays_jsonData) {
// Assuming jsonData is already in the correct format
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
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
routesByAirport = d3.group(routesData, d => d.origin_airport);
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
function showRoutes(svg, projection, airportsData, iataCode) {
const routes = routesByAirport.get(iataCode) || [];
routes.forEach(route => {
const origin = airportsData[iataCode];
const destination = airportsData[route.destination_airport];
if (destination) {
// Calculate start and end points
const startPoint = projection([+origin.longitude, +origin.latitude]);
const endPoint = projection([+destination.longitude, +destination.latitude]);

// Group for line and text
const routeGroup = svg.append("g").classed("route-group", true);

// Draw the line
routeGroup.append("line")
.attr("x1", startPoint[0])
.attr("y1", startPoint[1])
.attr("x2", endPoint[0])
.attr("y2", endPoint[1])
.attr("stroke", "gray")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "5, 5");

// Calculate midpoint for the text
const midX = (startPoint[0] + endPoint[0]) / 2;
const midY = (startPoint[1] + endPoint[1]) / 2;

// Text settings
const text = routeGroup.append("text")
.attr("x", midX)
.attr("y", midY)
.attr("dy", "0.3em") // Adjust vertical position
.attr("text-anchor", "middle")
.style("font-family", "Arial")
.text(route.count);

// Get the size of the text element for the background box
const textSize = text.node().getBBox();

// Add a background box behind the text
routeGroup.insert("rect", "text")
.attr("x", textSize.x - 2)
.attr("y", textSize.y - 2)
.attr("width", textSize.width + 4)
.attr("height", textSize.height + 4)
.attr("fill", "lightgray")
.attr("rx", 3); // Rounded corners

// Ensure the text is on top of the box
text.raise();
}
});
}

Insert cell
function hideRoutes(svg) {
svg.selectAll(".route-group").remove();
}

Insert cell
projection = d3.geoAlbersUsa().scale(1300).translate([487.5, 305]);
Insert cell
radiusScale = d3.scaleSqrt()
.domain(d3.extent(projectedPoints, d => d.DelayRate))
.range([2, 20]); // Min and max radius of circles
Insert cell
Insert cell
{
const svg = d3.select(map);
const circles = 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", "steelblue")
.attr("fill-opacity", 0.6)
.on("mouseover", (event, d) => showRoutes(svg, projection, airportsData, d.IataCode))
.on("mouseout", () => hideRoutes(svg));

// Append titles (tooltips) to each circle
circles.append("title")
.text(d => `IATA Code: ${d.IataCode}\nAirport: ${d.Airport}\nDelay Rate: ${d.DelayRate}\nFlights Count: ${d.FlightsCount}`);

addLegendToSVG(svg, radiusScale);


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
width = 2000
Insert cell
height = 200
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

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