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); } .title { font-size: 3rem; font-weight: 600; text-align: center; } """) as demo: gr.HTML(elem_classes="title", value="🌍") gr.HTML("Graffiti fonts") 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) demo.launch()