5
5
import pandas as pd
6
6
import streamlit as st
7
7
from streamlit_folium import st_folium
8
- from jinja2 import Template
8
+ from utils . pretty import pretty_dataframe , custom_map_zoom , custom_map_tooltip
9
9
10
- # #Page Setup
10
+
11
+ #Page Setup
11
12
st .set_page_config (
12
13
page_title = "MOEST Exam Center Calculator" ,
13
14
page_icon = ":school:" ,
24
25
}
25
26
</style>
26
27
"""
27
- legend_template = """
28
- {% macro html(this, kwargs) %}
29
- <div id='maplegend' class='maplegend'
30
- style='position: absolute; z-index: 9999; background-color: rgba(255, 255, 255, 0.65);
31
- border-radius: 6px; padding: 10px; font-size: 10.5px; right: 15px; top: 15px; border: 2px solid black;'>
32
- <div class='legend-scale'>
33
- <ul class='legend-labels'>
34
- <li style='font-size:18px;margin-bottom:5px;'><span style='background: #0096FF; opacity: 0.75;'></span>School</li>
35
- <li style='font-size:18px;'><span style='background: #C41E3A; opacity: 1.75;'></span>Center</li>
36
- </ul>
37
- </div>
38
- </div>
39
- <style type='text/css'>
40
- .maplegend .legend-scale ul {margin: 0; padding: 0; color: #0f0f0f;}
41
- .maplegend .legend-scale ul li {list-style: none; line-height: 18px; margin-bottom: 1.5px;}
42
- .maplegend ul.legend-labels li span {float: left; height: 16px; width: 16px; margin-right: 4.5px;}
43
- </style>
44
- {% endmacro %}
45
- """
28
+
46
29
# Render custom CSS
47
30
st .markdown (custom_css , unsafe_allow_html = True )
48
31
58
41
if 'filter_value' not in st .session_state :
59
42
st .session_state .filter_value = None
60
43
61
- #Maps setup
62
- m = folium .Map (location = [27.7007 , 85.3001 ], zoom_start = 12 , )
63
-
64
- # Add Legend in map
65
- macro = folium .MacroElement ()
66
- macro ._template = Template (legend_template )
67
- m .get_root ().add_child (macro )
68
-
69
- fg = folium .FeatureGroup (name = "Allocated Centers" )
70
-
71
44
#Sidebar
72
45
with st .sidebar :
73
-
74
46
add_side_header = st .sidebar .title ("Random Center Calculator" )
75
-
47
+
76
48
schools_file = st .sidebar .file_uploader ("Upload School/College file" , type = "tsv" )
77
49
centers_file = st .sidebar .file_uploader ("Upload Centers file" , type = "tsv" )
78
50
prefs_file = st .sidebar .file_uploader ("Upload Preferences file" , type = "tsv" )
79
51
80
52
calculate = st .sidebar .button ("Calculate Centers" , type = "primary" , use_container_width = True )
81
53
82
54
school_df = None
55
+ divider_color = "red"
83
56
# Tabs
84
57
tab1 , tab2 , tab3 , tab4 , tab5 = st .tabs ([
85
- "School Center" ,
86
- "School Center Distance" ,
87
- "View School Data" ,
88
- "View Centers Data" ,
89
- "View Pref Data"
58
+ "📍 School Center" ,
59
+ "🚌 School Center Distance" ,
60
+ "🏫 View School Data" ,
61
+ "📍 View Centers Data" ,
62
+ "🧮 View Pref Data"
90
63
])
91
64
92
- tab1 .subheader ("School Center" )
93
- tab2 .subheader ("School Center Distance" )
94
- tab3 .subheader ("School Data" )
95
- tab4 .subheader ("Center Data" )
96
- tab5 .subheader ("Pref Data" )
65
+ tab1 .subheader ("School Center" , divider = divider_color )
66
+ tab2 .subheader ("School Center Distance" , divider = divider_color )
67
+ tab3 .subheader ("School Data" , divider = divider_color )
68
+ tab4 .subheader ("Center Data" , divider = divider_color )
69
+ tab5 .subheader ("Pref Data" , divider = divider_color )
97
70
98
71
# Show data in Tabs as soon as the files are uploaded
99
72
if schools_file :
100
73
df = pd .read_csv (schools_file , sep = "\t " )
101
74
school_df = df
102
- tab3 .dataframe (df )
75
+ tab3 .dataframe (pretty_dataframe ( df ), use_container_width = True )
103
76
104
77
else :
105
78
tab3 .info ("Upload data to view it." , icon = "ℹ️" )
106
79
107
80
if centers_file :
108
81
df = pd .read_csv (centers_file , sep = "\t " )
109
- tab4 .dataframe (df )
82
+ tab4 .dataframe (pretty_dataframe ( df ), use_container_width = True )
110
83
else :
111
84
tab4 .info ("Upload data to view it." , icon = "ℹ️" )
112
85
113
86
if prefs_file :
114
87
df = pd .read_csv (prefs_file , sep = "\t " )
115
- tab5 .dataframe (df )
88
+ tab5 .dataframe (pretty_dataframe ( df ), use_container_width = True )
116
89
else :
117
90
tab5 .info ("Upload data to view it." , icon = "ℹ️" )
118
91
@@ -181,7 +154,7 @@ def save_file_to_temp(file_obj):
181
154
if 'school_center' in st .session_state .calculated_data :
182
155
df_school_center = pd .read_csv (st .session_state .calculated_data ['school_center' ], sep = "\t " )
183
156
allowed_filter_types = ['school' , 'center' ]
184
- st .session_state .filter_type = tab1 .radio ("Choose a filter type:" , allowed_filter_types )
157
+ st .session_state .filter_type = tab1 .radio ("Choose a filter type:" , allowed_filter_types , horizontal = True )
185
158
186
159
# Display an input field based on the selected filter type
187
160
if st .session_state .filter_type :
@@ -194,73 +167,94 @@ def save_file_to_temp(file_obj):
194
167
filter_options = [f"{ code } | { name } " for name , code in zip (df_school_center ['center' ].unique (), df_school_center ['cscode' ].unique ())]
195
168
196
169
# Display a selectbox for selection
197
- st .session_state .filter_value = tab1 .selectbox (f"Select a value for { st .session_state .filter_type } :" , filter_options )
170
+ st .session_state .filter_value = tab1 .selectbox (f"Select a value for { st .session_state .filter_type . capitalize () } :" , filter_options )
198
171
199
172
# Split the selected value to extract name and code
200
173
code , name = st .session_state .filter_value .split (' | ' )
201
174
202
175
# Filter the DataFrame based on the selected type and value
203
176
filtered_df = filter_data (df_school_center , st .session_state .filter_type , name )
204
177
205
- if st .session_state .filter_value :
178
+ with tab1 :
179
+ if st .session_state .filter_value :
206
180
# Remove thousand separator comma in scode and cscode
207
- styled_df = filtered_df .style .format ({
181
+ styled_df = pretty_dataframe ( filtered_df ) .style .format ({
208
182
"cscode" : lambda x : '{:.0f}' .format (x ),
209
- "scode" : lambda x : '{:.0f}' .format (x )
183
+ "scode" : lambda x : '{:.0f}' .format (x )
210
184
})
211
- tab1 .dataframe (styled_df , hide_index = True )
212
- tab1 .subheader ('Map' )
213
- tab1 .divider ()
214
- for index , center in filtered_df .iterrows ():
215
- fg .add_child (
216
- folium .Marker (
217
- location = [center .center_lat , center .center_long ],
218
- popup = f"{ (center .center ).title ()} \n Allocation: { center .allocation } " ,
219
- tooltip = f"{ center .center } " ,
220
- icon = folium .Icon (color = "red" )
221
- )
222
- )
223
-
224
- # Initialize an empty dictionary to store school coordinates
225
- filtered_schools = {}
226
-
185
+ st .dataframe (styled_df , hide_index = True , use_container_width = True )
186
+ st .markdown ("<br/><br/>" , unsafe_allow_html = True )
187
+ st .subheader ('Map' , divider = divider_color )
188
+
189
+ # Initialize data for map
190
+ map_data = filtered_df [['center_lat' , 'center_long' , 'center' , 'allocation' ]].copy ()
191
+ map_data .columns = ['lat' , 'long' , 'name' , 'allocation' ]
192
+ map_data ['type' ] = 'Center'
193
+
227
194
if school_df is not None :
228
-
229
- for index , row in school_df .iterrows ():
230
- scode = row ['scode' ]
231
- school_lat = row ['lat' ]
232
- school_long = row ['long' ]
233
-
234
- if scode in filtered_df ['scode' ].values :
235
- filtered_schools .setdefault (scode , []).append ((school_lat , school_long ))
236
-
237
- for index , school in filtered_df .iterrows ():
238
- lat_long_list = filtered_schools .get (school ['scode' ], [])
239
-
240
- for school_lat , school_long in lat_long_list :
241
- if school_lat is not None and school_long is not None :
242
- fg .add_child (
243
- folium .Marker (
244
- location = [school_lat , school_long ],
245
- popup = f"{ school ['school' ].title ()} \n Allocation: { school ['allocation' ]} " ,
246
- tooltip = f"{ school ['school' ]} " ,
247
- icon = folium .Icon (color = "blue" )
248
- )
249
- )
250
-
195
+ filter_school = school_df [school_df ['scode' ].isin (filtered_df ['scode' ]) & school_df ['lat' ].notnull () & school_df ['long' ].notnull ()]
196
+ school_map_data = filter_school [['lat' , 'long' , 'name-address' ]].copy ().rename (columns = {'name-address' : 'name' })
197
+ school_map_data ['type' ] = 'School'
198
+ map_data = pd .concat ([map_data , school_map_data ], ignore_index = True )
199
+
200
+ map_data .drop_duplicates (inplace = True )
201
+
202
+ try :
203
+ if st .session_state .map_type :
204
+ st .session_state .map_type = st .radio ("Choose a map type:" , ["cartodbpositron" , "openstreetmap" ], horizontal = True )
205
+ except :
206
+ st .session_state .map_type = "cartodbpositron"
207
+
208
+ show_heatmap = st .checkbox ("View allocation distribution" , value = False )
209
+
210
+ # Maps setup
211
+ m = folium .Map (
212
+ location = [map_data ['lat' ].mean (), map_data ['long' ].mean ()], # Center map on the mean of the lat and long
213
+ zoom_start = custom_map_zoom (map_data ['lat' ].values , map_data ['long' ].values ),
214
+ tiles = st .session_state .map_type
215
+ )
216
+
217
+ fg = folium .FeatureGroup (name = "Allocated Centers" )
218
+ for _ , row in map_data .iterrows ():
219
+ fg .add_child (folium .Marker (
220
+ location = [row ['lat' ], row ['long' ]],
221
+ tooltip = custom_map_tooltip (row ),
222
+ popup = custom_map_tooltip (row ),
223
+ icon = folium .CustomIcon (
224
+ "https://cdn-icons-png.flaticon.com/256/4996/4996117.png" if row ['type' ] == "School" else "https://cdn-icons-png.flaticon.com/256/15092/15092199.png" ,
225
+ icon_size = (38 , 40 ),
226
+ icon_anchor = (21 , 38 ),
227
+ shadow_image = "https://static.vecteezy.com/system/resources/thumbnails/013/169/090/small_2x/oval-shadow-for-object-or-product-png.png" ,
228
+ shadow_size = (28 , 30 ) if row ['type' ] == "School" else (22 , 24 ),
229
+ shadow_anchor = (8 , 19 ) if row ['type' ] == "School" else (8 , 13 ),
230
+ )
231
+ ))
251
232
m .add_child (fg )
252
- with tab1 :
253
- st_folium ( m , width = 1200 , height = 400 )
254
-
255
- tab1 .divider ()
256
- tab1 .subheader ('All Data' )
257
- tab1 .dataframe (df_school_center )
233
+
234
+ if show_heatmap :
235
+ max_allocation = map_data ['allocation' ].max ()
236
+ for _ , row in map_data [map_data .allocation > 0 ].iterrows ():
237
+ folium .CircleMarker (
238
+ location = [row ['lat' ], row ['long' ]],
239
+ radius = row ['allocation' ] / max_allocation * 25 ,
240
+ color = "#3c844a" ,
241
+ opacity = 0.35 ,
242
+ fill = True ,
243
+ fill_color = "green" ,
244
+ fill_opacity = 0.3
245
+ ).add_to (m )
246
+
247
+ st_folium ( m , width = 1200 , height = 400 )
248
+
249
+ st .markdown ("<br/><br/>" , unsafe_allow_html = True )
250
+ st .subheader ('All Data' , divider = divider_color )
251
+ st .dataframe (pretty_dataframe (df_school_center ), use_container_width = True )
258
252
else :
259
253
tab1 .info ("No calculated data available." , icon = "ℹ️" )
260
254
261
255
if 'school_center_distance' in st .session_state .calculated_data :
262
256
df = pd .read_csv (st .session_state .calculated_data ['school_center_distance' ], sep = "\t " )
263
- tab2 .dataframe (df )
257
+ tab2 .dataframe (pretty_dataframe ( df ), use_container_width = True )
264
258
265
259
else :
266
260
tab2 .error ("School Center Distance file not found." )
0 commit comments