Spaces:
Running
Running
import gradio as gr | |
import pandas as pd | |
from datetime import datetime | |
import matplotlib.pyplot as plt | |
from pywaffle import Waffle | |
import math | |
import numpy as np | |
# Load the life expectancy data from the World Bank | |
# This is a direct link to the CSV data for life expectancy at birth, total (years) | |
file = "life_expectancy_2023_world_back_data.csv" | |
# Read the CSV data, skipping the first 4 rows of metadata | |
try: | |
df = pd.read_csv(file) | |
life_expectancy_data = df[['Country','Life Expectancy at Birth']].copy() | |
# life_expectancy_data.columns = ['Country', 'Life Expectancy'] | |
life_expectancy_data.dropna(inplace=True) | |
countries = sorted(life_expectancy_data['Country'].unique()) | |
except Exception as e: | |
print(f"Error loading data: {e}") | |
countries = ["Error loading country data"] | |
life_expectancy_data = pd.DataFrame(columns=['Country', 'Life Expectancy']) | |
def create_life_calendar(name, birth_year, country): | |
""" | |
Creates a graphical calendar of life, with each year represented | |
as a row of 52 dots (weeks). | |
""" | |
# --- Input Validation and Data Fetching --- | |
if not all([name, birth_year, country]) or country.startswith("Error"): | |
return None, "Please provide your name, birth year, and select a country." | |
if life_expectancy_data.empty: | |
return None, "Life expectancy data is unavailable. Cannot generate calendar." | |
try: | |
birth_year = int(birth_year) | |
country_data = life_expectancy_data[life_expectancy_data['Country'] == country] | |
if country_data.empty: | |
return None, f"Sorry, life expectancy data for {country} is not available." | |
life_expectancy = int(country_data['Life Expectancy at Birth'].iloc[0]) | |
except (ValueError, TypeError): | |
return None, "Please enter a valid birth year." | |
# --- Calculations --- | |
now = datetime.now() | |
current_year = now.year | |
current_week = now.isocalendar()[1] | |
age = current_year - birth_year | |
# --- Plotting Setup --- | |
fig, ax = plt.subplots(figsize=(10, life_expectancy / 10), dpi=120) | |
# Define colors | |
past_color = '#d9534f' # Red | |
future_color = '#5cb85c' # Green | |
dot_size = 10 | |
weeks_in_year = 52 | |
# --- Vectorized Plotting --- | |
# Create arrays for all x and y coordinates | |
all_weeks = np.tile(np.arange(1, weeks_in_year + 1), life_expectancy) | |
# y-coordinates: these need to be in the order of plotting (top to bottom) | |
# so, for year 1, y is life_expectancy - 1; for year life_expectancy, y is 0 | |
all_years_plot_coord = np.repeat(np.arange(life_expectancy - 1, -1, -1), weeks_in_year) | |
# Determine colors for all dots | |
colors = [] | |
for year_display in range(1, life_expectancy + 1): # Iterate through years from 1 to life_expectancy | |
if year_display < age: | |
colors.extend([past_color] * weeks_in_year) | |
elif year_display == age: | |
# Weeks up to current_week are past, the rest are future | |
colors.extend([past_color] * (current_week)) # current_week is 1-indexed | |
colors.extend([future_color] * (weeks_in_year - current_week)) | |
else: | |
colors.extend([future_color] * weeks_in_year) | |
# Plot all dots at once | |
ax.scatter(all_weeks, all_years_plot_coord, c=colors, s=dot_size, marker='o') | |
# --- Formatting and Labels --- | |
# Add year/age labels to the y-axis | |
ax.set_yticks(range(0, life_expectancy, 5)) | |
# Labels should correspond to the 'Age' represented by the y-coordinate | |
# If y = life_expectancy - year, then year = life_expectancy - y | |
# So the age label for y-coordinate 'i' is 'life_expectancy - i' | |
ax.set_yticklabels([str(life_expectancy - i) for i in range(0, life_expectancy, 5)]) | |
ax.set_ylabel("Age", fontsize=12) | |
# Configure x-axis for weeks | |
ax.set_xticks([1, 13, 26, 39, 52]) | |
ax.set_xticklabels(["Week 1", "Week 13", "Week 26", "Week 39", "Week 52"]) | |
ax.set_xlabel("Week of the Year", fontsize=12) | |
ax.xaxis.tick_top() | |
ax.xaxis.set_label_position('top') | |
# Remove the plot frame/spines for a cleaner look | |
for spine in ['left', 'right', 'bottom']: | |
ax.spines[spine].set_visible(False) | |
# Set plot limits | |
ax.set_xlim(0, weeks_in_year + 1) | |
ax.set_ylim(-1, life_expectancy) # Ensure y-axis covers all years, with a small buffer | |
# Create a custom legend | |
legend_elements = [ | |
plt.Line2D([0], [0], marker='o', color='w', label='Past Week', markerfacecolor=past_color, markersize=10), | |
plt.Line2D([0], [0], marker='o', color='w', label='Future Week', markerfacecolor=future_color, markersize=10) | |
] | |
ax.legend(handles=legend_elements, loc='center left', bbox_to_anchor=(1.02, 0.5), fontsize=12) | |
fig.tight_layout() | |
weeks_left = (life_expectancy - age) * 52 - current_week | |
message = f"Hello {name}, based on a life expectancy of {life_expectancy} in {country}, you have approximately {weeks_left:,} weeks remaining." | |
return fig, message | |
# Create the Gradio interface | |
with gr.Blocks(css=".center-text {text-align: center;}",theme=gr.themes.Soft()) as demo: | |
gr.Markdown( | |
""" | |
# Last Sunday | |
Get reminded of how many Sundays remain :) | |
This app shows you a visualization of how many Sunday are remaining in your life. It's created to remind oneself that time in life is limited and is not to be wasted. | |
It's very easy to use. You simply enter your name and date of birth. Taking life expectancy as 80 years, it tells you how many weeks are left until you die. | |
Inspired by the PARAS CHOPRA [Last Sunday](https://chromewebstore.google.com/detail/the-last-sunday-reminder/aiojhapcgfgmiacbbjfgedhlcchmpelh?hl=en) app. | |
""" | |
,elem_classes="center-text" | |
) | |
with gr.Row(): | |
name_input = gr.Textbox(label="Your Name") | |
birth_year_input = gr.Number(label="Your Birth Year", minimum=1900, maximum=datetime.now().year) | |
country_input = gr.Dropdown(choices=countries, label="Your Country") | |
# output_text = gr.Textbox(label="Your Remaining Sundays") | |
# with gr.Row(): | |
# expired_input = gr.Number(label="Expired Sundays (Red Dots)", value=1560) | |
# remaining_input = gr.Number(label="Remaining Sundays (Green Dots)", value=2600) | |
generate_button = gr.Button("Generate My Life Calendar", variant="primary") | |
with gr.Column(): | |
output_plot = gr.Plot() | |
output_text = gr.Label() | |
generate_button.click( | |
fn=create_life_calendar, | |
inputs=[name_input, birth_year_input, country_input], | |
outputs=[output_plot, output_text] | |
) | |
demo.launch() | |