Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| from nomad_data import country_emoji_map, data, terrain_emoji_map | |
| df = pd.DataFrame(data) | |
| 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(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" | |
| ) | |
| 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 | |
| ) | |
| 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 | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 🧳 Digital Nomad Tips") | |
| gr.Markdown("- Look for places with digital nomad visas for longer stays \n" | |
| "- Consider internet speed if you need to attend video meetings \n" | |
| "- Balance cost of living with quality of life for the best experience \n" | |
| "- Some newer nomad destinations may have incomplete data") | |
| gr.Markdown("### 🎯 Find Your Ideal Destination") | |
| with gr.Row(): | |
| with gr.Column(): | |
| priority = gr.CheckboxGroup( | |
| ["Best Quality of Life", "Fastest Internet", "Most Affordable", "Balance of All Factors"], | |
| label="What are Your Priorities?", | |
| value=["Balance of All Factors"] | |
| ) | |
| find_btn = gr.Button("Find My Ideal Destination", variant="primary") | |
| recommendation = gr.Textbox(label="Recommended Location", lines=3) | |
| def recommend_location(priorities, max_budget): | |
| if not priorities: | |
| return "Please select at least one priority to get a recommendation." | |
| budget_filtered_df = df[df["Monthly Cost Living (USD)"] <= max_budget] | |
| budget_warning = "" | |
| if len(budget_filtered_df) == 0: | |
| budget_filtered_df = df | |
| budget_warning = "⚠️ No cities match your budget. Showing best options regardless of cost.\n\n" | |
| recommendations = [] | |
| if "Best Quality of Life" in priorities: | |
| top_city = budget_filtered_df.sort_values("Quality of Life", ascending=False).iloc[0] | |
| terrain_emoji = terrain_emoji_map.get(top_city['Terrain'], top_city['Terrain']).split()[0] | |
| message = f"{terrain_emoji} {top_city['City']}, {top_city['Country']} - Quality of Life: {top_city['Quality of Life']}\n" | |
| message += f"Monthly Cost: ${top_city['Monthly Cost Living (USD)']}\n" | |
| message += f"Key Feature: {top_city['Key Feature']}" | |
| recommendations.append(message) | |
| if "Fastest Internet" in priorities: | |
| top_city = budget_filtered_df.sort_values("Internet Speed (Mbps)", ascending=False).iloc[0] | |
| terrain_emoji = terrain_emoji_map.get(top_city['Terrain'], top_city['Terrain']).split()[0] | |
| message = f"{terrain_emoji} {top_city['City']}, {top_city['Country']} - Internet Speed: {top_city['Internet Speed (Mbps)']} Mbps\n" | |
| message += f"Monthly Cost: ${top_city['Monthly Cost Living (USD)']}\n" | |
| message += f"Key Feature: {top_city['Key Feature']}" | |
| recommendations.append(message) | |
| if "Most Affordable" in priorities: | |
| top_city = budget_filtered_df.sort_values("Monthly Cost Living (USD)", ascending=True).iloc[0] | |
| terrain_emoji = terrain_emoji_map.get(top_city['Terrain'], top_city['Terrain']).split()[0] | |
| message = f"{terrain_emoji} {top_city['City']}, {top_city['Country']} - Monthly Cost: ${top_city['Monthly Cost Living (USD)']}\n" | |
| message += f"Quality of Life: {top_city['Quality of Life']}, Internet: {top_city['Internet Speed (Mbps)']} Mbps\n" | |
| message += f"Key Feature: {top_city['Key Feature']}" | |
| recommendations.append(message) | |
| if "Balance of All Factors" in priorities: | |
| df_temp = budget_filtered_df.copy() | |
| df_temp['quality_norm'] = df_temp['Quality of Life'] / 10 | |
| df_temp['internet_norm'] = df_temp['Internet Speed (Mbps)'] / 400 | |
| df_temp['cost_norm'] = 1 - (df_temp['Monthly Cost Living (USD)'] / 4000) | |
| df_temp['composite_score'] = (df_temp['quality_norm'] + df_temp['internet_norm'] + df_temp['cost_norm']) / 3 | |
| top_city = df_temp.sort_values("composite_score", ascending=False).iloc[0] | |
| terrain_emoji = terrain_emoji_map.get(top_city['Terrain'], top_city['Terrain']).split()[0] | |
| message = f"{terrain_emoji} {top_city['City']}, {top_city['Country']} - Balanced Choice\n" | |
| message += f"Quality: {top_city['Quality of Life']}, Internet: {top_city['Internet Speed (Mbps)']} Mbps, Cost: ${top_city['Monthly Cost Living (USD)']}\n" | |
| message += f"Key Feature: {top_city['Key Feature']}" | |
| recommendations.append(message) | |
| return budget_warning + "\n\n".join(recommendations) | |
| find_btn.click(recommend_location, inputs=[priority, cost_slider], outputs=recommendation) | |
| cost_slider.change(recommend_location, inputs=[priority, cost_slider], outputs=recommendation) | |
| demo.launch() | |