File size: 12,645 Bytes
74ead6a
 
 
3c02b8d
74ead6a
 
 
25bfcbf
 
 
 
 
 
 
 
 
 
 
 
74ead6a
 
 
 
 
dc030d6
 
74ead6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c02b8d
 
74ead6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c02b8d
 
 
 
 
 
 
74ead6a
 
e12bd69
74ead6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1fa456e
 
 
 
 
9b990a4
 
 
 
 
 
 
74ead6a
1fa456e
cf6903d
9b990a4
74ead6a
 
9b990a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74ead6a
9b990a4
 
 
 
 
 
 
3c02b8d
 
 
 
6924eba
3c02b8d
c598099
9b990a4
 
 
 
 
 
 
 
 
74ead6a
c598099
74ead6a
 
3c02b8d
74ead6a
 
 
 
 
 
852316b
460511e
74ead6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c02b8d
9b990a4
 
 
 
 
 
 
 
3c02b8d
 
 
 
 
 
 
 
9b990a4
 
 
 
 
 
 
 
 
 
 
dc030d6
9b990a4
 
 
 
 
 
 
 
 
3c02b8d
 
 
9b990a4
 
 
 
 
 
 
 
 
 
 
 
 
 
74ead6a
9b990a4
 
3c02b8d
9b990a4
 
 
 
3c02b8d
9b990a4
 
 
 
3c02b8d
9b990a4
 
 
 
3c02b8d
9b990a4
 
 
 
3c02b8d
9b990a4
 
 
 
3c02b8d
 
 
 
 
 
9b990a4
 
 
e12bd69
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
import gradio as gr
import pandas as pd

from nomad_data import country_emoji_map, data, terrain_emoji_map

df = pd.DataFrame(data)

js_func = """
function refresh() {
    const url = new URL(window.location);

    if (url.searchParams.get('__theme') !== 'dark') {
        url.searchParams.set('__theme', 'dark');
        window.location.href = url.href;
    }
}
"""


def style_quality_of_life(val):
    """Style the Quality of Life column with color gradient from red to green"""
    if pd.isna(val):
        return 'background-color: rgba(200, 200, 200, 0.2); color: #999; font-style: italic;'
    
    min_val = 5.0 
    max_val = 9.0  
    
    normalized = (val - min_val) / (max_val - min_val)
    normalized = max(0, min(normalized, 1))
    
    percentage = int(normalized * 100)
    
    if normalized < 0.5:
        start_color = f"rgba(255, {int(255 * (normalized * 2))}, 0, 0.3)"
        end_color = "rgba(255, 255, 255, 0)"
    else:
        start_color = f"rgba({int(255 * (1 - (normalized - 0.5) * 2))}, 255, 0, 0.3)"
        end_color = "rgba(255, 255, 255, 0)"
    
    return f'background: linear-gradient(to right, {start_color} {percentage}%, {end_color} {percentage}%)'

def style_internet_speed(val):
    """Style the Internet Speed column from red (slow) to green (fast)"""
    if pd.isna(val):
        return 'background-color: rgba(200, 200, 200, 0.2); color: #999; font-style: italic;'
    
    min_val = 20   # Slow internet
    max_val = 300  # Fast internet
    
    normalized = (val - min_val) / (max_val - min_val)
    normalized = max(0, min(normalized, 1))
    
    percentage = int(normalized * 100)
    
    if normalized < 0.5:
        start_color = f"rgba(255, {int(255 * (normalized * 2))}, 0, 0.3)"
        end_color = "rgba(255, 255, 255, 0)"
    else:
        start_color = f"rgba({int(255 * (1 - (normalized - 0.5) * 2))}, 255, 0, 0.3)"
        end_color = "rgba(255, 255, 255, 0)"
    
    return f'background: linear-gradient(to right, {start_color} {percentage}%, {end_color} {percentage}%)'

def style_dataframe(df):
    """Apply styling to the entire dataframe"""
    styled_df = df.copy()
    
    styled_df['Terrain'] = styled_df['Terrain'].apply(lambda x: terrain_emoji_map.get(x, x) if pd.notna(x) else x)
    
    styler = styled_df.style
    
    styler = styler.applymap(style_quality_of_life, subset=['Quality of Life'])
    styler = styler.applymap(style_internet_speed, subset=['Internet Speed (Mbps)'])
    
    styler = styler.highlight_null(props='color: #999; font-style: italic; background-color: rgba(200, 200, 200, 0.2)')
    
    styler = styler.format({
        'Quality of Life': lambda x: f'{x:.1f}' if pd.notna(x) else 'Data Not Available',
        'Internet Speed (Mbps)': lambda x: f'{x:.1f}' if pd.notna(x) else 'Data Not Available',
        'Monthly Cost Living (USD)': lambda x: f'${x:.0f}' if pd.notna(x) else 'Data Not Available',
        'Visa Length (Months)': lambda x: f'{x:.0f}' if pd.notna(x) else 'Data Not Available',
        'Visa Cost (USD)': lambda x: f'${x:.0f}' if pd.notna(x) else 'Data Not Available',
        'Growth Trend (5 Years)': lambda x: f'{x}' if pd.notna(x) else 'Data Not Available'
    })
    
    return styler

def filter_data(country, max_cost):
    """Filter data based on country and maximum cost of living"""
    filtered_df = df.copy()
    
    if country and country != "All":
        filtered_df = filtered_df[filtered_df["Country"] == country]
    
    if max_cost < df["Monthly Cost Living (USD)"].max():
        cost_mask = (filtered_df["Monthly Cost Living (USD)"] <= max_cost) | (filtered_df["Monthly Cost Living (USD)"].isna())
        filtered_df = filtered_df[cost_mask]
    
    return style_dataframe(filtered_df)

def get_unique_values(column):
    unique_values = ["All"] + sorted(df[column].unique().tolist())
    return unique_values

def get_country_with_emoji(column):
    choices_with_emoji = ["โœˆ๏ธ All"]
    for c in df[column].unique():
        if c in country_emoji_map:
            choices_with_emoji.append(country_emoji_map[c])
        else:
            choices_with_emoji.append(c)
    return sorted(choices_with_emoji)

def get_terrain_with_emoji():
    terrains = ["โœจ All"]
    for terrain in sorted(df["Terrain"].unique()):
        if terrain in terrain_emoji_map:
            terrains.append(terrain_emoji_map[terrain])
    return terrains

styled_df = style_dataframe(df)

with gr.Blocks(js=js_func, css="""
    .gradio-container .table-wrap {
        font-family: 'Inter', sans-serif;
    }
    .gradio-container table td, .gradio-container table th {
        text-align: left;
    }
    .gradio-container table th {
        background-color: #f3f4f6;
        font-weight: 600;
    }
    /* Style for null values */
    .null-value {
        color: #999;
        font-style: italic;
        background-color: rgba(200, 200, 200, 0.2);
    }
    .title {
        font-size: 3rem;
        font-weight: 600;
        text-align: center;
    }
    
    .app-subtitle {
        color: rgba(255, 255, 255, 0.8);
        font-size: 1.2rem;
        margin-bottom: 15px;
    } 

""") as demo:
    gr.HTML(elem_classes="title", value="๐ŸŒ")
    gr.HTML("<img src='https://see.fontimg.com/api/rf5/JpZqa/MWMyNzc2ODk3OTFlNDk2OWJkY2VjYTIzNzFlY2E4MWIudHRm/bm9tYWQgZGVzdGluYXRpb25z/super-feel.png?r=fs&h=130&w=2000&fg=e2e2e2&bg=FFFFFF&tb=1&s=65' alt='Graffiti fonts'></a>")
    gr.Markdown("Discover the best places for digital nomads around the globe")
    
    with gr.Row():
        with gr.Column(scale=1):
            cost_slider = gr.Slider(
                minimum=500,
                maximum=4000,
                value=4000,
                step=100,
                label="๐Ÿ’ฐ Maximum Monthly Cost of Living (USD)"
            )
            
            min_internet = gr.Slider(
                minimum=0,
                maximum=400,
                value=0,
                step=10,
                label="๐ŸŒ Minimum Internet Speed (Mbps)"
            )
            
            min_quality = gr.Slider(
                minimum=5,
                maximum=10,
                value=5,
                step=0.1,
                label="โญ Minimum Quality of Life"
            )
        
        with gr.Column(scale=1):
            country_dropdown = gr.Dropdown(
                choices=get_country_with_emoji("Country"),
                value="โœˆ๏ธ All",
                label="๐ŸŒ Filter by Country"
            )
            
            terrain_dropdown = gr.Dropdown(
                choices=get_terrain_with_emoji(),
                value="โœจ All",
                label="๐Ÿž๏ธ Filter by Terrain"
            )
            
        with gr.Column(scale=1):
            visa_filter = gr.CheckboxGroup(
                choices=["Has Digital Nomad Visa", "Visa Length โ‰ฅ 12 Months"],
                label="๐Ÿ›‚ Visa Requirements"
            )
            
            special_features = gr.CheckboxGroup(
                choices=["Coastal Cities", "Cultural Hotspots", "Affordable (<$1000/month)"],
                label="โœจ Special Features"
            )
    
    
    data_table = gr.Dataframe(
        value=styled_df,
        datatype=["str", "str", "str", "number", "number", "number", "str", "number", "number", "str", "str"],
        max_height=600,
        interactive=False,
        show_copy_button=True,
        show_row_numbers=True,
        show_search=True,
        show_fullscreen_button=True,
        pinned_columns=3,
        column_widths=[100, 100, 100]
    )
    
    def process_country_filter(country, cost):
        if country and country.startswith("โœˆ๏ธ All"):
            country = "All"
        else:
            for emoji_code in ["๐Ÿ‡ง๐Ÿ‡ท", "๐Ÿ‡ญ๐Ÿ‡บ", "๐Ÿ‡บ๐Ÿ‡พ", "๐Ÿ‡ต๐Ÿ‡น", "๐Ÿ‡ฌ๐Ÿ‡ช", "๐Ÿ‡น๐Ÿ‡ญ", "๐Ÿ‡ฆ๐Ÿ‡ช", "๐Ÿ‡ช๐Ÿ‡ธ", "๐Ÿ‡ฎ๐Ÿ‡น", "๐Ÿ‡จ๐Ÿ‡ฆ", "๐Ÿ‡จ๐Ÿ‡ด", "๐Ÿ‡ฒ๐Ÿ‡ฝ", "๐Ÿ‡ฏ๐Ÿ‡ต", "๐Ÿ‡ฐ๐Ÿ‡ท"]:
                if country and emoji_code in country:
                    country = country.split(" ", 1)[1]
                    break
                    
        filtered_df = df.copy()
        
        if country and country != "All":
            filtered_df = filtered_df[filtered_df["Country"] == country]
        
        if cost < df["Monthly Cost Living (USD)"].max():
            cost_mask = (filtered_df["Monthly Cost Living (USD)"] <= cost) & (filtered_df["Monthly Cost Living (USD)"].notna())
        
            filtered_df = filtered_df[cost_mask]
        
        return style_dataframe(filtered_df)
    
    def apply_advanced_filters(country, cost, min_internet_speed, min_qol, visa_reqs, features, terrain):
        if country and country.startswith("โœˆ๏ธ All"):
            country = "All"
        else:
            for emoji_code in ["๐Ÿ‡ง๐Ÿ‡ท", "๐Ÿ‡ญ๐Ÿ‡บ", "๐Ÿ‡บ๐Ÿ‡พ", "๐Ÿ‡ต๐Ÿ‡น", "๐Ÿ‡ฌ๐Ÿ‡ช", "๐Ÿ‡น๐Ÿ‡ญ", "๐Ÿ‡ฆ๐Ÿ‡ช", "๐Ÿ‡ช๐Ÿ‡ธ", "๐Ÿ‡ฎ๐Ÿ‡น", "๐Ÿ‡จ๐Ÿ‡ฆ", "๐Ÿ‡จ๐Ÿ‡ด", "๐Ÿ‡ฒ๐Ÿ‡ฝ", "๐Ÿ‡ฏ๐Ÿ‡ต", "๐Ÿ‡ฐ๐Ÿ‡ท"]:
                if country and emoji_code in country:
                    country = country.split(" ", 1)[1]
                    break
        
        if terrain and terrain.startswith("โœจ All"):
            terrain = "All"
        else:
            for emoji in ["๐Ÿ–๏ธ", "โ›ฐ๏ธ", "๐Ÿ™๏ธ", "๐Ÿœ๏ธ", "๐ŸŒด", "๐Ÿ๏ธ", "๐ŸŒฒ", "๐ŸŒพ"]:
                if terrain and emoji in terrain:
                    terrain = terrain.split(" ", 1)[1]
                    break
        
        filtered_df = df.copy()
        
        if country and country != "All":
            filtered_df = filtered_df[filtered_df["Country"] == country]
        
        if cost < df["Monthly Cost Living (USD)"].max():
            cost_mask = (filtered_df["Monthly Cost Living (USD)"] <= cost) & (filtered_df["Monthly Cost Living (USD)"].notna())
            filtered_df = filtered_df[cost_mask]
        
        if min_internet_speed > 0:
            filtered_df = filtered_df[filtered_df["Internet Speed (Mbps)"] >= min_internet_speed]

        if min_qol > 5:
            filtered_df = filtered_df[filtered_df["Quality of Life"] >= min_qol]
        
        if "Has Digital Nomad Visa" in visa_reqs:
            filtered_df = filtered_df[filtered_df["Digital Nomad Visa"] == "Yes"]
        
        if "Visa Length โ‰ฅ 12 Months" in visa_reqs:
            filtered_df = filtered_df[filtered_df["Visa Length (Months)"] >= 12]
        
        if terrain and terrain != "All":
            filtered_df = filtered_df[filtered_df["Terrain"] == terrain]
        
        if "Coastal Cities" in features:
            coastal_keywords = ["coast", "beach", "sea", "ocean"]
            mask = filtered_df["Key Feature"].str.contains("|".join(coastal_keywords), case=False, na=False)
            filtered_df = filtered_df[mask]
        
        if "Cultural Hotspots" in features:
            cultural_keywords = ["cultur", "art", "histor", "heritage"]
            mask = filtered_df["Key Feature"].str.contains("|".join(cultural_keywords), case=False, na=False)
            filtered_df = filtered_df[mask]
        
        if "Affordable (<$1000/month)" in features:
            filtered_df = filtered_df[filtered_df["Monthly Cost Living (USD)"] < 1000]
        
        return style_dataframe(filtered_df)
    
    country_dropdown.change(
        apply_advanced_filters, 
        [country_dropdown, cost_slider, min_internet, min_quality, visa_filter, special_features, terrain_dropdown], 
        data_table
    )
    cost_slider.change(
        apply_advanced_filters, 
        [country_dropdown, cost_slider, min_internet, min_quality, visa_filter, special_features, terrain_dropdown], 
        data_table
    )
    min_internet.change(
        apply_advanced_filters, 
        [country_dropdown, cost_slider, min_internet, min_quality, visa_filter, special_features, terrain_dropdown], 
        data_table
    )
    min_quality.change(
        apply_advanced_filters, 
        [country_dropdown, cost_slider, min_internet, min_quality, visa_filter, special_features, terrain_dropdown], 
        data_table
    )
    visa_filter.change(
        apply_advanced_filters, 
        [country_dropdown, cost_slider, min_internet, min_quality, visa_filter, special_features, terrain_dropdown], 
        data_table
    )
    special_features.change(
        apply_advanced_filters, 
        [country_dropdown, cost_slider, min_internet, min_quality, visa_filter, special_features, terrain_dropdown], 
        data_table
    )
    terrain_dropdown.change(
        apply_advanced_filters, 
        [country_dropdown, cost_slider, min_internet, min_quality, visa_filter, special_features, terrain_dropdown], 
        data_table
    )
    
demo.launch()