Skip to content

Commit 72b1574

Browse files
author
Xing Han Lu
authored
Merge pull request #609 from plotly/add-port-dashboard
Add the appsilon port analytics app from original repo Former-commit-id: 7f264c9
2 parents d895b29 + ba595b2 commit 72b1574

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+4524
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://github.com/plotly/heroku-buildpack-python
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/venv
2+
3+
/node_modules
4+
package-lock.json
5+
6+
# cache files
7+
.idea
8+
/.idea
9+
/app/__pycache__
10+
/app/ui/__pycache__
11+
/config/__pycache__
12+
13+
# generated HTML file
14+
/data/index.html
15+
.vscode
16+
*__pycache__

apps/dash-port-analytics/Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: gunicorn main:server
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## How to add Data to the app
2+
1. Inside the `/data` folder locate the CSV file of interest - there are 3 CSV files for separate tabs + 1 CSV file - **add your data there**
3+
2. Under `config/constants.py` add a latitude and lognitude pair for your location
4+
3. Under `config/strings.py` add a variable name for your city - `CITY_<cityname>`
5+
4. Under `app/helpers.py` change `get_port_dropdown_values()` and `get_lat_long_for_port()` functions accordingly
6+
5. Launch the app - `python main.py`
7+
6. Enjoy!
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import pandas as pd
2+
from branca.element import Template, MacroElement
3+
from config import constants, strings, styles
4+
5+
6+
def filter_by_port_vessel_and_time(
7+
df: pd.DataFrame, port: str, vessel_type: str, year: int, month: int
8+
) -> pd.DataFrame:
9+
"""
10+
Repetitive function, used to perform initial filtering for data displayed on the dashboard.
11+
12+
:param df: Pandas DataFrame, input data
13+
:param port: str, port of interest
14+
:param vessel_type: str, vessel type of interest
15+
:param year: int, year of interest
16+
:param month: int, month of interest
17+
:return: Pandas DataFrame, filtered
18+
"""
19+
if vessel_type == strings.CITY_ALL:
20+
return df[
21+
(df["port"] == port) & (df["year"] == year) & (df["month"] == month)
22+
].copy()
23+
return df[
24+
(df["port"] == port)
25+
& (df["ship_type"] == vessel_type)
26+
& (df["year"] == year)
27+
& (df["month"] == month)
28+
].copy()
29+
30+
31+
def filter_by_vessel_and_time(
32+
df: pd.DataFrame, vessel_type: str, year: int, month: int
33+
) -> pd.DataFrame:
34+
"""
35+
Repetitive function, used to perform initial filtering for data displayed on the dashboard.
36+
37+
:param df: Pandas DataFrame, input data
38+
:param vessel_type: str, vessel type of interest
39+
:param year: int, year of interest
40+
:param month: int, month of interest
41+
:return: Pandas DataFrame, filtered
42+
"""
43+
if vessel_type == strings.CITY_ALL:
44+
return df[(df["year"] == year) & (df["month"] == month)].copy()
45+
return df[
46+
(df["ship_type"] == vessel_type) & (df["year"] == year) & (df["month"] == month)
47+
].copy()
48+
49+
50+
def filter_by_vessel_and_port(
51+
df: pd.DataFrame, port1: str, port2: str, vessel_type: str
52+
) -> pd.DataFrame:
53+
"""
54+
Repetitive function, used to perform initial data filtering for comparison tab.
55+
56+
:param df: Pandas DataFrame, input data
57+
:param port1: str, first port to compare
58+
:param port2: str, second port to compare
59+
:param vessel_type: str, vessel type of interest
60+
:return: Pandas DataFrame, filtered
61+
"""
62+
if vessel_type == strings.CITY_ALL:
63+
return df[df["port"].isin([port1, port2])].copy()
64+
return df[
65+
(df["ship_type"] == vessel_type) & (df["port"].isin([port1, port2]))
66+
].copy()
67+
68+
69+
def get_dropdown_items(df: pd.DataFrame, attribute: str) -> list:
70+
"""
71+
Returns a list of dropdown elements for a given attribute name.
72+
73+
:param df: Pandas DataFrame object which contains the attribute
74+
:param attribute: str, can be either port, vessel_type, year, or month
75+
:return: list of unique attribute values
76+
"""
77+
if attribute == "port":
78+
return df["port"].unique().tolist()
79+
elif attribute == "vessel_type":
80+
return ["All", *sorted(df["ship_type"].unique().tolist())]
81+
elif attribute == "year":
82+
return df["year"].unique().tolist()
83+
elif attribute == "month":
84+
return df["month"].unique().tolist()
85+
else:
86+
raise KeyError("Invalid value for `argument`")
87+
88+
89+
def get_port_dropdown_values(curr_port_1: str, curr_port_2: str) -> list:
90+
"""
91+
Returns list of possible port options for both dropdowns.
92+
93+
:param curr_port_1: str, current value for the first port dropdown
94+
:param curr_port_2: str, current value for the second port dropdown
95+
:return: list, possible port values for both dropdowns
96+
"""
97+
ports_dpd_1 = [
98+
strings.CITY_GDANSK,
99+
strings.CITY_GDYNIA,
100+
strings.CITY_KALINGRAD,
101+
strings.CITY_KLAIPEDA,
102+
strings.CITY_STPETERBURG,
103+
]
104+
ports_dpd_2 = [
105+
strings.CITY_GDANSK,
106+
strings.CITY_GDYNIA,
107+
strings.CITY_KALINGRAD,
108+
strings.CITY_KLAIPEDA,
109+
strings.CITY_STPETERBURG,
110+
]
111+
ports_dpd_1.remove(curr_port_2)
112+
ports_dpd_2.remove(curr_port_1)
113+
return [ports_dpd_1, ports_dpd_2]
114+
115+
116+
def get_lat_long_for_port(port: str) -> list:
117+
"""
118+
Returns a list of lat lon parameters for the map on the first tab.
119+
120+
:param port: str, a location you want coordinates for
121+
:return: list - [latitude, longitude]
122+
"""
123+
mappings = {
124+
strings.CITY_GDANSK: constants.LOC_GDANSK,
125+
strings.CITY_GDYNIA: constants.LOC_GDYNIA,
126+
strings.CITY_KALINGRAD: constants.LOC_KALINGRAD,
127+
strings.CITY_KLAIPEDA: constants.LOC_KLAIPEDA,
128+
strings.CITY_STPETERBURG: constants.LOC_STPETRSBURG,
129+
}
130+
return mappings[port]
131+
132+
133+
def generate_map_legend() -> MacroElement:
134+
"""
135+
Generates a legend for the map.
136+
137+
:return: MacroElement, html added to the map
138+
"""
139+
template = """
140+
{% macro html(this, kwargs) %}
141+
142+
<!doctype html>
143+
<html lang="en">
144+
<head>
145+
<meta charset="utf-8">
146+
<meta name="viewport" content="width=device-width, initial-scale=1">
147+
<title>Dashboard</title>
148+
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
149+
150+
<link rel="preconnect" href="https://fonts.gstatic.com">
151+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
152+
153+
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
154+
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
155+
156+
157+
</head>
158+
<body>
159+
160+
161+
<div id='maplegend' class='maplegend'
162+
style='
163+
position: absolute;
164+
z-index:9999;
165+
background-color:rgba(255, 255, 255, 0.8);
166+
border: 1px solid #D4D4D4;
167+
border-radius:6px;
168+
padding: 10px;
169+
font-size:14px;
170+
right: 10px;
171+
bottom: 23px;
172+
'>
173+
174+
<div class='legend-scale'>
175+
<ul class='legend-labels'>
176+
<li><span style='background:#E87272;'></span>Unspecified</li>
177+
<li><span style='background:#7a0091;'></span>Navigation</li>
178+
<li><span style='background:#11498A;'></span>Fishing</li>
179+
<li><span style='background:#1A6D9B;'></span>Tug</li>
180+
<li><span style='background:#12A5B0;'></span>Passenger</li>
181+
<li><span style='background:#3A9971;'></span>Cargo</li>
182+
<li><span style='background:#79BD00;'></span>Tanker</li>
183+
<li><span style='background:#DBB657;'></span>Pleasure</li>
184+
</ul>
185+
</div>
186+
</div>
187+
188+
</body>
189+
</html>
190+
191+
<style type='text/css'>
192+
* {
193+
font-family: "Roboto", sans-serif;
194+
}
195+
196+
.maplegend .legend-title {
197+
text-align: left;
198+
margin-bottom: 5px;
199+
font-weight: bold;
200+
font-size: 90%;
201+
}
202+
.maplegend .legend-scale ul {
203+
margin: 0;
204+
margin-bottom: 5px;
205+
padding: 0;
206+
float: left;
207+
list-style: none;
208+
}
209+
.maplegend .legend-scale ul li {
210+
font-size: 80%;
211+
list-style: none;
212+
margin-left: 0;
213+
line-height: 18px;
214+
margin-bottom: 2px;
215+
}
216+
.maplegend ul.legend-labels li span {
217+
display: block;
218+
float: left;
219+
height: 16px;
220+
width: 30px;
221+
margin-right: 5px;
222+
margin-left: 0;
223+
border: 1px solid #999;
224+
}
225+
.maplegend .legend-source {
226+
font-size: 80%;
227+
color: #777;
228+
clear: both;
229+
}
230+
.maplegend a {
231+
color: #777;
232+
}
233+
234+
.maplegend .legend-scale ul:last-child {
235+
margin-bottom: 0px;
236+
}
237+
.maplegend .legend-scale ul li:last-child {
238+
margin-bottom: 0px;
239+
}
240+
241+
</style>
242+
{% endmacro %}"""
243+
244+
macro = MacroElement()
245+
macro._template = Template(template)
246+
return macro
247+
248+
249+
def generate_color(category: str) -> str:
250+
"""
251+
Returns a color hex code for a given ship category.
252+
253+
:param category: str, ship category (vessel type)
254+
:return: str, hex code for the color
255+
"""
256+
mappings = {
257+
strings.STYPE_UNSPECIFIED: styles.COLOR_APPSILON_1,
258+
strings.STYPE_NAVIGATION: styles.COLOR_APPSILON_2,
259+
strings.STYPE_FISHING: styles.COLOR_APPSILON_3,
260+
strings.STYPE_TUG: styles.COLOR_APPSILON_4,
261+
strings.STYPE_PASSENGER: styles.COLOR_APPSILON_5,
262+
strings.STYPE_CARGO: styles.COLOR_APPSILON_6,
263+
strings.STYPE_TANKER: styles.COLOR_APPSILON_7,
264+
strings.STYPE_PLEASURE: styles.COLOR_APPSILON_8,
265+
}
266+
return mappings[category]

0 commit comments

Comments
 (0)