Spaces:
Running
Running
| import gradio as gr | |
| import pandas as pd | |
| from nomad_data import country_emoji_map, data | |
| # Create dataframe from imported data | |
| df = pd.DataFrame(data) | |
| # Create styling functions | |
| def style_quality_of_life(val): | |
| """Style the Quality of Life column with color gradient from red to green""" | |
| if pd.isna(val): | |
| # Special styling for null/missing values | |
| return 'background-color: rgba(200, 200, 200, 0.2); color: #999; font-style: italic;' | |
| # Define min and max values for Quality of Life (typically on a scale of 0-10) | |
| min_val = 5.0 # Anything below this will be bright red | |
| max_val = 9.0 # Anything above this will be bright green | |
| # Normalize value between 0 and 1 | |
| normalized = (val - min_val) / (max_val - min_val) | |
| # Clamp between 0 and 1 | |
| normalized = max(0, min(normalized, 1)) | |
| # Calculate percentage fill for gradient | |
| percentage = int(normalized * 100) | |
| # Create a linear gradient based on the normalized value | |
| if normalized < 0.5: | |
| # Red to yellow gradient | |
| start_color = f"rgba(255, {int(255 * (normalized * 2))}, 0, 0.3)" | |
| end_color = "rgba(255, 255, 255, 0)" | |
| else: | |
| # Yellow to green gradient | |
| 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): | |
| # Special styling for null/missing values | |
| return 'background-color: rgba(200, 200, 200, 0.2); color: #999; font-style: italic;' | |
| # Define min and max values | |
| min_val = 20 # Slow internet | |
| max_val = 300 # Fast internet | |
| # Normalize value between 0 and 1 | |
| normalized = (val - min_val) / (max_val - min_val) | |
| # Clamp between 0 and 1 | |
| normalized = max(0, min(normalized, 1)) | |
| # Calculate percentage fill for gradient | |
| percentage = int(normalized * 100) | |
| # Create a linear gradient based on the normalized value | |
| if normalized < 0.5: | |
| # Red to yellow gradient | |
| start_color = f"rgba(255, {int(255 * (normalized * 2))}, 0, 0.3)" | |
| end_color = "rgba(255, 255, 255, 0)" | |
| else: | |
| # Yellow to green gradient | |
| 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""" | |
| # Create a copy to avoid SettingWithCopyWarning | |
| styled_df = df.copy() | |
| # Convert to Styler object | |
| styler = styled_df.style | |
| # Apply styles to specific columns | |
| styler = styler.applymap(style_quality_of_life, subset=['Quality of Life']) | |
| styler = styler.applymap(style_internet_speed, subset=['Internet Speed (Mbps)']) | |
| # Highlight null values in all columns | |
| styler = styler.highlight_null(props='color: #999; font-style: italic; background-color: rgba(200, 200, 200, 0.2)') | |
| # Format numeric columns | |
| 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] | |
| # Filter by maximum cost of living (and handle null values) | |
| if max_cost < df["Monthly Cost Living (USD)"].max(): | |
| # Include rows where cost is less than max_cost OR cost is null | |
| 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) | |
| # Function to get unique values for dropdowns with "All" option | |
| def get_unique_values(column): | |
| unique_values = ["All"] + sorted(df[column].unique().tolist()) | |
| return unique_values | |
| # Add country emojis for the dropdown | |
| 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) | |
| # Initial styled dataframe | |
| 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); | |
| } | |
| """) as demo: | |
| gr.Markdown("# ๐ Digital Nomad Destinations") | |
| gr.Markdown("Explore top digital nomad locations around the world. The bars in numeric columns indicate relative values - longer bars are better!") | |
| with gr.Row(): | |
| country_dropdown = gr.Dropdown( | |
| choices=get_country_with_emoji("Country"), | |
| value="โ๏ธ All", | |
| label="๐ Filter by Country" | |
| ) | |
| cost_slider = gr.Slider( | |
| minimum=500, | |
| maximum=4000, | |
| value=4000, | |
| step=100, | |
| label="๐ฐ Maximum Monthly Cost of Living (USD)" | |
| ) | |
| data_table = gr.Dataframe( | |
| value=styled_df, | |
| datatype=["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=2 | |
| ) | |
| # Update data when filters change | |
| def process_country_filter(country, cost): | |
| # Remove emoji from country name if present | |
| 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() | |
| # Filter by country | |
| if country and country != "All": | |
| filtered_df = filtered_df[filtered_df["Country"] == country] | |
| # Filter by cost with special handling for nulls | |
| 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) | |
| country_dropdown.change(process_country_filter, [country_dropdown, cost_slider], data_table) | |
| cost_slider.change(process_country_filter, [country_dropdown, cost_slider], data_table) | |
| gr.Markdown("### ๐ Data Visualization Guide") | |
| gr.Markdown("The table above uses colorful gradient bars to help you quickly identify: \n" | |
| "- **๐ Quality of Life**: Longer green bars indicate higher quality of life \n" | |
| "- **๐ Internet Speed**: Longer green bars indicate faster internet connections \n" | |
| "- **๐ต Cost of Living**: Values shown as dollar amounts without color coding \n" | |
| "- **โ Missing Data**: Displayed as *Data Not Available* with a light gray background") | |
| 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") | |
| demo.launch() | |