Skip to content

Commit e557290

Browse files
Merge pull request #8 from cherrylchico/main
use graph from ETL for port calls
2 parents 2aeb52c + a5c0786 commit e557290

File tree

2 files changed

+259
-1
lines changed

2 files changed

+259
-1
lines changed

docs/ais/ais_trade.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ We get the difference between the departure and arrival displacements to get the
2121
2222
## Port Arrivals
2323
24-
<div class="flourish-embed flourish-chart" data-src="visualisation/19836784?2274258"><script src="https://public.flourish.studio/resources/embed.js"></script><noscript><img src="https://public.flourish.studio/visualisation/19836784/thumbnail" width="100%" alt="chart visualization" /></noscript></div>
24+
<div style="width: 100%; max-width: 800px; margin: 0 auto;">
25+
<iframe src="../interactive/ais/port calls.html"
26+
frameborder="0"
27+
scrolling="no"
28+
style="width: 100%; height: 650px;">
29+
</iframe>
30+
</div>
2531
2632
## Trade Volume
2733
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Port Arrivals</title>
6+
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
7+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
8+
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
9+
<style>
10+
html, body {
11+
height: 100%;
12+
margin-bottom: 5px;
13+
padding: 2px;
14+
font-family: 'Open Sans', sans-serif;
15+
background-color: white;
16+
}
17+
h2 {
18+
font-size: 20px;
19+
margin-bottom: 10px;
20+
}
21+
#chart {
22+
width: 100%;
23+
height: 500px; /* or use 100vh for full screen */
24+
margin: 0;
25+
padding: 0;
26+
}
27+
label, select {
28+
font-size: 14px;
29+
margin-right: 10px;
30+
font-family: 'Open Sans', sans-serif;
31+
}
32+
select {
33+
border: none;
34+
border-bottom: 1px solid #888;
35+
background-color: transparent;
36+
font-size: 14px;
37+
padding: 5px 0;
38+
outline: none;
39+
font-family: 'Open Sans', sans-serif;
40+
color: #333;
41+
cursor: pointer;
42+
}
43+
</style>
44+
</head>
45+
<body>
46+
47+
<div id="titleRow" style="
48+
margin-bottom: 10px;
49+
">
50+
<h2 id="chartTitle" style="
51+
font-size: 20px;
52+
font-weight: bold;
53+
margin: 0;
54+
font-family: 'Open Sans', sans-serif;
55+
">
56+
Port Arrivals Derived from AIS
57+
</h2>
58+
<p id="chartSubtitle" style="
59+
font-size: 16px;
60+
color: #666;
61+
margin: 4px 0 0;
62+
font-family: 'Open Sans', sans-serif;
63+
">
64+
Counts, Monthly
65+
</p>
66+
</div>
67+
68+
<label for="countrySelect">Country:</label>
69+
<select id="countrySelect"></select>
70+
71+
<label for="portSelect">Port:</label>
72+
<select id="portSelect"></select>
73+
74+
<div id="chart" style="width:100%;position:relative;"></div>
75+
<div id="footer" style="
76+
width: 100%;
77+
display: flex;
78+
justify-content: space-between;
79+
align-items: center;
80+
font-family: 'Open Sans', sans-serif;
81+
font-size: 12px;
82+
color: #888;
83+
">
84+
<div style="margin-bottom:10px;">
85+
Source: <a href="https://www.worldbank.org" target="_blank" style="text-decoration: underline; color: #888;">World Bank Group</a>
86+
</div>
87+
<img src="https://public.flourish.studio/uploads/71a85b0c-7f8c-453b-bc80-22b945333d58.png" style="height: 25px; object-fit: contain; margin-right: 5px; margin-bottom:10px;" alt="Logo">
88+
</div>
89+
90+
91+
92+
<script>
93+
const hlCategories = ["Cargo", "Fishing", "Passenger", "Tanker"];
94+
let fullData = [];
95+
96+
Papa.parse("https://raw.githubusercontent.com/cherrylchico/pacific-observatory-ais-data/main/data/monthly%20port%20calls.csv", {
97+
download: true,
98+
header: true,
99+
skipEmptyLines: true,
100+
complete: function(results) {
101+
fullData = results.data.filter(d =>
102+
hlCategories.includes(d.HL) &&
103+
d["port call"] &&
104+
!isNaN(parseInt(d["port call"]))
105+
);
106+
populateCountryDropdown();
107+
}
108+
});
109+
110+
function populateCountryDropdown() {
111+
const countries = [...new Set(fullData.map(d => d.Country))].sort();
112+
const countrySelect = document.getElementById("countrySelect");
113+
countrySelect.innerHTML = "";
114+
115+
const allOption = document.createElement("option");
116+
allOption.value = "__ALL__";
117+
allOption.textContent = "All Countries";
118+
countrySelect.appendChild(allOption);
119+
120+
countries.forEach(c => {
121+
const option = document.createElement("option");
122+
option.value = c;
123+
option.textContent = c;
124+
countrySelect.appendChild(option);
125+
});
126+
127+
countrySelect.addEventListener("change", populatePortDropdown);
128+
countrySelect.value = "__ALL__";
129+
populatePortDropdown();
130+
}
131+
132+
function populatePortDropdown() {
133+
const selectedCountry = document.getElementById("countrySelect").value;
134+
const portSelect = document.getElementById("portSelect");
135+
136+
portSelect.innerHTML = "";
137+
138+
const allOption = document.createElement("option");
139+
allOption.value = "__ALL__";
140+
allOption.textContent = "All Ports";
141+
portSelect.appendChild(allOption);
142+
143+
if (selectedCountry === "__ALL__") {
144+
portSelect.disabled = true;
145+
} else {
146+
const ports = [...new Set(fullData.filter(d => d.Country === selectedCountry).map(d => d.Port))].sort();
147+
ports.forEach(p => {
148+
const option = document.createElement("option");
149+
option.value = p;
150+
option.textContent = p;
151+
portSelect.appendChild(option);
152+
});
153+
portSelect.disabled = false;
154+
}
155+
156+
portSelect.addEventListener("change", updateChart);
157+
portSelect.value = "__ALL__";
158+
updateChart();
159+
}
160+
161+
function updateChart() {
162+
const selectedCountry = document.getElementById("countrySelect").value;
163+
const selectedPort = document.getElementById("portSelect").value;
164+
165+
let filtered = fullData.filter(d => hlCategories.includes(d.HL));
166+
167+
if (selectedCountry !== "__ALL__") {
168+
filtered = filtered.filter(d => d.Country === selectedCountry);
169+
if (selectedPort !== "__ALL__") {
170+
filtered = filtered.filter(d => d.Port === selectedPort);
171+
}
172+
}
173+
174+
const yearList = [...new Set(filtered.map(d => d.year))].sort();
175+
const allMonths = [];
176+
yearList.forEach(year => {
177+
for (let m = 1; m <= 12; m++) {
178+
allMonths.push({ year: year, month: m });
179+
}
180+
});
181+
182+
const traces = hlCategories.map(hl => {
183+
const x = [];
184+
const y = [];
185+
186+
allMonths.forEach(({ year, month }) => {
187+
const entries = filtered.filter(d =>
188+
d.year == year &&
189+
parseInt(d.month) == month &&
190+
d.HL === hl
191+
);
192+
const total = entries.reduce((sum, d) => sum + parseInt(d["port call"]), 0);
193+
if (total > 0) {
194+
x.push(`${year}-${month}`);
195+
y.push(total);
196+
}
197+
});
198+
199+
return {
200+
x: x,
201+
y: y,
202+
name: hl,
203+
type: 'scatter',
204+
mode: 'lines'
205+
};
206+
});
207+
208+
const layout = {
209+
autosize: true,
210+
margin: {
211+
l: 40,
212+
r: 20,
213+
t: 10,
214+
b: 0
215+
},
216+
yaxis: {
217+
showline:true,
218+
linecolor: '#ccc',
219+
griddash: 'dot',
220+
gridcolor: '#ccc',
221+
ticks: 'outside',
222+
tickcolor: '#ccc',
223+
zeroline: false
224+
},
225+
xaxis: {
226+
rangeslider: { visible: true},
227+
title: '',
228+
showgrid: false,
229+
ticks: 'outside',
230+
tickson: 'boundaries',
231+
tickcolor:'#ccc',
232+
showline: true,
233+
linecolor : '#ccc'
234+
},
235+
legend: {
236+
orientation: 'h',
237+
x: 0,
238+
y: 1,
239+
},
240+
hoverlabel:{
241+
font:{family: 'Open Sans, sans-serif'}
242+
},
243+
};
244+
245+
Plotly.newPlot('chart', traces, layout, {responsive: true});
246+
247+
const chartDiv = document.getElementById('chart');
248+
249+
}
250+
</script>
251+
</body>
252+
</html>

0 commit comments

Comments
 (0)