File size: 6,642 Bytes
1118be3
 
 
 
 
 
aace014
1118be3
 
 
aa4e7a7
1118be3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d6957b6
1118be3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d6957b6
1118be3
 
d6957b6
 
1118be3
 
 
d6957b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1118be3
 
 
 
d6957b6
 
 
1118be3
 
d6957b6
1118be3
 
 
 
 
 
 
 
 
 
 
 
 
d6957b6
1118be3
 
 
 
 
 
 
d6957b6
1118be3
d6957b6
1118be3
 
 
 
 
 
d6957b6
 
1118be3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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()