Repository navigation
Expand file tree
/
Copy pathmap.js
More file actions
213 lines (178 loc) · 6.83 KB
/
Copy pathmap.js
File metadata and controls
213 lines (178 loc) · 6.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7.9.0/+esm";
const svg = d3.select('#map').select('svg');
let stations = [];
let timeFilter = -1;
let trips = []; // Declare trips in a broader scope
let radiusScale; // Declare radiusScale in a broader scope
let circles;
let stationFlow = d3.scaleQuantize().domain([0, 1]).range([0, 0.5, 1]);
function computeStationTraffic(stations, trips) {
const departures = d3.rollup(
trips,
(v) => v.length,
(d) => d.start_station_id
);
const arrivals = d3.rollup(
trips,
(v) => v.length,
(d) => d.end_station_id
);
return stations.map((station) => {
let id = station.short_name;
station.arrivals = arrivals.get(id) ?? 0;
station.departures = departures.get(id) ?? 0;
station.totalTraffic = (station.arrivals + station.departures);
return station;
});
};
function minutesSinceMidnight(date) {
return date.getHours() * 60 + date.getMinutes();
};
function filterTripsbyTime(trips, timeFilter) {
return timeFilter === -1
? trips
: trips.filter((trip) => {
const startedMinutes = minutesSinceMidnight(trip.started_at);
const endedMinutes = minutesSinceMidnight(trip.ended_at);
return (
Math.abs(startedMinutes - timeFilter) <= 60 ||
Math.abs(endedMinutes - timeFilter) <= 60
);
});
};
mapboxgl.accessToken = 'pk.eyJ1IjoiZm9saWNrcyIsImEiOiJjbTc5a3IwdG4wMWszMm1weWx4am5pNml1In0.fgO6EPaoZ2qcKxzwDof-6w';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-71.09415, 42.36027],
zoom: 12,
minZoom: 5,
maxZoom: 18,
projection: 'globe'
});
const timeSlider = document.getElementById('time-filter');
const selectedTime = document.getElementById('selected-time');
const anyTimeLabel = document.getElementById('any-time');
function formatTime(minutes) {
const date = new Date(0, 0, 0, Math.floor(minutes / 60), minutes % 60);
return date.toLocaleString('en-US', { timeStyle: 'short' });
}
function updateTimeDisplay() {
timeFilter = Number(timeSlider.value);
if (timeFilter === -1) {
selectedTime.textContent = '';
anyTimeLabel.style.display = 'block';
} else {
selectedTime.textContent = formatTime(timeFilter);
anyTimeLabel.style.display = 'none';
}
updateScatterPlot(timeFilter);
}
// Define updateScatterPlot in the global scope, before it's called
function updateScatterPlot(timeFilter) {
if (!stations || stations.length === 0) {
console.warn("Stations data not yet loaded. UpdateScatterPlot cannot proceed.");
return;
}
if (!trips || trips.length === 0) {
console.warn("Trips data not yet loaded. UpdateScatterPlot cannot proceed.");
return;
}
// Get only the trips that match the selected time filter
const filteredTrips = filterTripsbyTime(trips, timeFilter);
// Recompute station traffic based on the filtered trips
const filteredStations = computeStationTraffic(stations, filteredTrips);
timeFilter === -1 ? radiusScale.range([0, 25]) : radiusScale.range([3, 50]);
// Update the scatterplot by adjusting the radius of circles
circles
.data(filteredStations, (d) => d.short_name)
.join('circle') // Ensure the data is bound correctly
.attr('r', (d) => radiusScale(d.totalTraffic)) // Update circle sizes
.style('--departure-ratio', (d) =>
stationFlow(d.departures / d.totalTraffic),
);
}
map.on('load', async () => {
map.addSource('boston_route', {
type: 'geojson',
data: 'https://bostonopendata-boston.opendata.arcgis.com/datasets/boston::existing-bike-network-2022.geojson'
});
map.addLayer({
id: 'bike-lanes',
type: 'line',
source: 'boston_route',
paint: {
'line-color': '#32D400',
'line-width': 5,
'line-opacity': 0.6
}
});
map.addSource('cambridge_route', {
type: 'geojson',
data: 'https://raw.githubusercontent.com/cambridgegis/cambridgegis_data/main/Recreation/Bike_Facilities/RECREATION_BikeFacilities.geojson'
});
map.addLayer({
id: 'cambridge-lanes',
type: 'line',
source: 'cambridge_route',
paint: {
'line-color': '#32D400',
'line-width': 5,
'line-opacity': 0.6
}
});
const jsonurl = "https://dsc106.com/labs/lab07/data/bluebikes-stations.json";
d3.json(jsonurl).then(async jsonData => {
trips = await d3.csv( // Assign trips to the broader scope
'https://dsc106.com/labs/lab07/data/bluebikes-traffic-2024-03.csv',
(trip) => {
trip.started_at = new Date(trip.started_at);
trip.ended_at = new Date(trip.ended_at);
return trip;
},
);
stations = computeStationTraffic(jsonData.data.stations, trips);
// Define radiusScale AFTER stations data is processed, inside the 'load' event
radiusScale = d3
.scaleSqrt()
.domain([0, d3.max(stations, (d) => d.totalTraffic)])
.range([0, 25]);
// Create the SVG circles after the data is loaded and processed
circles = svg.selectAll('circle')
.data(stations, (d) => d.short_name)
.enter()
.append('circle')
.style("--departure-ratio", d => stationFlow(d.departures / d.totalTraffic))
.attr('r', (d) => radiusScale(d.totalTraffic)) // Radius now based on scaled totalTraffic
.attr('fill', 'steelblue') // Circle fill color
.attr('stroke', 'white') // Circle border color
.attr('stroke-width', 1) // Circle border thickness
.attr('opacity', 0.8)
.each(function(d) {
// Add <title> for browser tooltips
d3.select(this)
.append('title')
.text(`${d.totalTraffic} trips (${d.departures} departures, ${d.arrivals} arrivals)`);
}); // Circle opacity
function getCoords(station) {
const point = new mapboxgl.LngLat(+station.lon, +station.lat);
const { x, y } = map.project(point);
return { cx: x, cy: y };
}
function updatePositions() {
circles
.attr('cx', d => getCoords(d).cx)
.attr('cy', d => getCoords(d).cy);
}
updatePositions();
map.on('move', updatePositions);
map.on('zoom', updatePositions);
map.on('resize', updatePositions);
map.on('moveend', updatePositions);
updateScatterPlot(timeFilter);
});
});
document.addEventListener('DOMContentLoaded', () => {
timeSlider.addEventListener('input', updateTimeDisplay);
updateTimeDisplay();
});