Skip to content

Commit 509f0a0

Browse files
update
1 parent 2fa2075 commit 509f0a0

3 files changed

Lines changed: 120 additions & 17 deletions

File tree

css/main.css

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,38 @@ a:hover {
6767
top:50%;
6868
left:50%;
6969
background-color:#fff;
70-
display:hidden;
70+
display:none;
7171
padding:20px;
7272
font-size: 1.1em;
7373
}
74+
#provdetails {
75+
width:700px;
76+
max-height:500px;
77+
margin-left:-370px;
78+
margin-top:-210px;
79+
position:absolute;
80+
z-index:1050;
81+
border-radius:2px;
82+
top:50%;
83+
left:50%;
84+
background-color:#fff;
85+
display:none;
86+
padding:20px;
87+
font-size: 1.1em;
88+
}
89+
#crossings {
90+
max-height:440px;
91+
overflow-y:auto;
92+
overflow-x:hidden;
93+
}
7494
#wrapperAbout {
7595
position:absolute;
7696
width:100%;
7797
height:100%;
7898
background-color:#333;
7999
opacity: 0.5;
80100
z-index:1001;
81-
display:hidden;
101+
display:none;
82102
}
83103
#moreinfo {
84104
cursor:pointer;
@@ -95,6 +115,7 @@ a:hover {
95115
.color-lightgreen{ background-color: #91cf60; color: black; }
96116
.color-yellow { background-color: #ffffbf; color: black; }
97117
.color-orange { background-color: #fc8d59; color: black; }
98-
.color-red { background-color: #d73027; color: white; }
118+
.color-red { background-color: #d53e4f; color: white; }
119+
.color-deepred { background-color: #a50026; color: white; }
99120
.table-cell { padding: 6px 10px; text-align: right; }
100121
.table-head { font-weight: bold; text-align: left; padding: 6px 10px; background-color: #eee; }

index.html

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,19 @@
4848
<div id="about">
4949
<img id='close' style='float:right;cursor:pointer' src='img/close.png' />
5050
<h2>About This Project</h2>
51-
<p>This interactive visualization presents data on travelers entering the United States from Canada via land border crossings. By selecting a port (marker) on the map, users can explore trends in the number of passengers in personal vehicles as well as pedestrians crossing into the U.S. at that location.
52-
</p><p>
53-
The data is sourced from U.S. Customs and Border Protection (CBP) and is publicly available via the <a href="https://www.cbp.gov/document/stats/traveler-and-conveyance-statistics" target="_blank">Traveler and Conveyance Statistics portal</a>. This platform includes only entries from Canadian land borders and excludes travelers arriving by air and water as well as passengers in commercial trucks.
54-
</p>
55-
<p>
56-
Developed by <a href="https://platial.science">The Platial Analysis Lab</a>.</p><p>Contact Grant McKenzie at <a href="mailto:grant.mckenzie@mcgill.ca">grant.mckenzie@mcgill.ca</a> with any questions.</p>
51+
<p>
52+
This interactive visualization presents data on travelers entering the United States from Canada via land border crossings. By selecting a port (marker) on the map, users can explore trends in the number of passengers in personal vehicles as well as pedestrians crossing into the U.S. at that location.
53+
</p><p>
54+
The data is sourced from U.S. Customs and Border Protection (CBP) and is publicly available via the <a href="https://www.cbp.gov/document/stats/traveler-and-conveyance-statistics" target="_blank">Traveler and Conveyance Statistics portal</a>. This platform includes only entries from Canadian land borders and excludes travelers arriving by air and water as well as passengers in commercial trucks.
55+
</p>
56+
<p>
57+
Developed by <a href="https://platial.science">The Platial Analysis Lab</a>.</p><p>Contact Grant McKenzie at <a href="mailto:grant.mckenzie@mcgill.ca">grant.mckenzie@mcgill.ca</a> with any questions.
58+
</p>
59+
</div>
60+
<div id="provdetails">
61+
<img id='close2' style='float:right;cursor:pointer' src='img/close.png' />
62+
<div style='margin-top:0;font-weight:bold;font-size:1.3em;margin-bottom:10px' id='provname'>Border Crossings by Province</div>
63+
<div id='crossings' style='text-align:center'></div>
5764
</div>
5865
<div id="wrapperAbout"></div>
5966
<div id="map"></div>

js/main.js

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22
document.getElementById("wrapperAbout").addEventListener("click", function(event) {
33
document.getElementById('wrapperAbout').style.display = "none";
44
document.getElementById('about').style.display = "none";
5+
document.getElementById('provdetails').style.display = "none";
56
});
67

78
document.getElementById("close").addEventListener("click", function(event) {
89
document.getElementById('wrapperAbout').style.display = "none";
910
document.getElementById('about').style.display = "none";
1011
});
1112

13+
document.getElementById("close2").addEventListener("click", function(event) {
14+
document.getElementById('wrapperAbout').style.display = "none";
15+
document.getElementById('provdetails').style.display = "none";
16+
});
17+
1218
document.getElementById("moreinfo").addEventListener("click", function(event) {
1319
document.getElementById('wrapperAbout').style.display = "block";
1420
document.getElementById('about').style.display = "block";
@@ -33,12 +39,14 @@
3339
const portMarkers = {};
3440

3541
var _stats = null;
42+
var allports = null;
3643
let myChartInstance = null;
3744

3845
// Step 1: Load and plot initial markers
3946
fetch('data/ports.json')
4047
.then(response => response.json())
4148
.then(portData => {
49+
allports = portData;
4250
Object.entries(portData).forEach(([portcode, port]) => {
4351
const lat = parseFloat(port.lat);
4452
const lng = parseFloat(port.lng);
@@ -158,6 +166,15 @@
158166
}
159167
}
160168

169+
function getColorClass(value) {
170+
if (value >= 5) return "color-green";
171+
else if (value >= 0) return "color-lightgreen";
172+
else if (value >= -10) return "color-yellow";
173+
else if (value >= -20) return "color-orange";
174+
else if (value >= -30) return "color-red";
175+
else return "color-deepred";
176+
}
177+
161178
fetch('data/prov.json')
162179
.then(response => response.json())
163180
.then(provData => {
@@ -166,13 +183,7 @@ fetch('data/prov.json')
166183
const monthKeys = ["1", "2", "3"];
167184

168185
// Define breakpoints for 5 classes (green = high positive, red = low negative)
169-
function getColorClass(value) {
170-
if (value >= 5) return "color-green";
171-
else if (value >= 0) return "color-lightgreen";
172-
else if (value >= -10) return "color-yellow";
173-
else if (value >= -25) return "color-orange";
174-
else return "color-red";
175-
}
186+
176187

177188
// Sort province names alphabetically
178189
const sortedProvinces = Object.keys(provData).sort();
@@ -183,7 +194,7 @@ fetch('data/prov.json')
183194
html += '</tr></thead><tbody>';
184195

185196
sortedProvinces.forEach(prov => {
186-
html += `<tr><td class="table-cell" style="text-align:left;">${prov}</td>`;
197+
html += `<tr><td class="table-cell" style="cursor:pointer;color:#32a6c3;text-align:left;" onclick="loadprov(\'${prov}\')");">${prov}</td>`;
187198
monthKeys.forEach(k => {
188199
const val = provData[prov][k];
189200
const cls = getColorClass(val);
@@ -195,3 +206,67 @@ fetch('data/prov.json')
195206
html += '</tbody></table>';
196207
container.innerHTML = html;
197208
});
209+
210+
var ports_provs = [];
211+
fetch('data/ports.geojson')
212+
.then(response => response.json())
213+
.then(data => {
214+
ports_provs = data;
215+
for(feat in data.features) {
216+
prov = data.features[feat].properties.PRNAME.split("/")[0].trim();
217+
portcode = data.features[feat].properties.portcode;
218+
if (!Array.isArray(ports_provs[prov])) {
219+
ports_provs[prov] = [];
220+
}
221+
ports_provs[prov].push(portcode);
222+
}
223+
})
224+
.catch(error => {
225+
console.error('Error loading GeoJSON:', error);
226+
});
227+
228+
229+
function loadprov(prov) {
230+
document.getElementById('wrapperAbout').style.display = "block";
231+
document.getElementById('provdetails').style.display = "block";
232+
document.getElementById('provname').innerHTML = "Border Crossings in "+prov;
233+
234+
var s = null;
235+
var missing = 0;
236+
content = '<table><thead><tr><th class="table-head" style="width:220px">Border Crossing</th>';
237+
const months = ["January", "February", "March"];
238+
months.forEach(m => content += `<th class="table-head">${m}</th>`);
239+
content += '</tr></thead><tbody>';
240+
241+
for (port in ports_provs[prov]) {
242+
pp = String(ports_provs[prov][port]).padStart(4, '0');
243+
if (typeof allports[pp] === "object" && allports[pp] !== null && typeof _stats[pp] === "object" && _stats[pp] !== null) {
244+
s = _stats[pp];
245+
f = allports[pp];
246+
content += '<tr><td class="table-cell" style="width:350px;text-align:left;">' + f.name + " ("+f.state+") </td>";
247+
248+
for(month in s[2025]) {
249+
if (month < 5) {
250+
var x = Math.round((s[2024][month].vehicle.car - s[2025][month].vehicle.car)/s[2024][month].vehicle.car*-1000)/10;
251+
const cls = getColorClass(x);
252+
content += `<td class="table-cell ${cls}">${x.toFixed(1)}%</td>`;
253+
254+
}
255+
}
256+
} else {
257+
missing++;
258+
}
259+
content += "</tr>";
260+
}
261+
content += '</tbody></table>';
262+
if (missing > 0) {
263+
content += "<div style='margin-top:10px;text-align:left;color:#660000'>";
264+
if (missing == 1) {
265+
content += "<i>Details for "+missing + " crossing is not reported due to not enough data.</i>"
266+
} else if (missing > 1) {
267+
content += "<i>Details for "+missing + " crossings are not reported due to not enough data.</i>"
268+
}
269+
content += "</div>";
270+
}
271+
document.getElementById('crossings').innerHTML = content;
272+
}

0 commit comments

Comments
 (0)