215 lines
7.0 KiB
Python
215 lines
7.0 KiB
Python
import pandas as pd
|
|
import vobject
|
|
import requests
|
|
from datetime import datetime, date
|
|
import matplotlib.pyplot as plt
|
|
import calmap
|
|
import streamlit as st
|
|
import plotly.express as px
|
|
import plotly.graph_objects as go
|
|
import json
|
|
import os
|
|
|
|
|
|
|
|
# Function to load data from a JSON file
|
|
def load_data(file_path):
|
|
try:
|
|
with open(file_path, 'r') as file:
|
|
if os.path.getsize(file_path) == 0: return {}
|
|
return json.load(file)
|
|
except FileNotFoundError:
|
|
print("settings file not found")
|
|
return {}
|
|
except :
|
|
print("settings file invalid")
|
|
return {}
|
|
|
|
|
|
# Function to save data to a JSON file
|
|
def save_data(file_path, data):
|
|
with open(file_path, 'w') as file:
|
|
json.dump(data, file)
|
|
|
|
|
|
# Function to create a calendar heatmap for a specific year
|
|
def create_calendar_heatmap(event_series, year):
|
|
# Filter the series to include only the specific year
|
|
event_series_year = event_series[event_series.index.year == year]
|
|
|
|
# Create a complete date range for the year
|
|
all_dates_year = pd.date_range(start=f'{year}-01-01', end=f'{year}-12-31')
|
|
|
|
# Reindex the Series to include all dates in the year and fill missing dates with zero
|
|
event_series_year = event_series_year.reindex(all_dates_year, fill_value=0)
|
|
|
|
# Create a DataFrame for Plotly
|
|
df_year = pd.DataFrame({'Date': event_series_year.index, 'Events': event_series_year.values})
|
|
|
|
# Generate day of the week and week of the year
|
|
df_year['DayOfWeek'] = df_year['Date'].dt.dayofweek
|
|
df_year['WeekOfYear'] = df_year['Date'].dt.isocalendar().week
|
|
df_year['Month'] = df_year['Date'].dt.strftime('%b')
|
|
|
|
# Create the Plotly figure
|
|
fig = go.Figure(data=go.Heatmap(
|
|
z=df_year['Events'],
|
|
x=df_year['WeekOfYear'],
|
|
y=df_year['DayOfWeek'],
|
|
colorscale='YlGn',
|
|
showscale=True,
|
|
colorbar=dict(
|
|
title='# of Events',
|
|
orientation='h', # Set the color scale to horizontal
|
|
len=0.3, # Adjust the length of the color scale
|
|
thickness=10,
|
|
x=0.98,
|
|
y=1,
|
|
xanchor="right",
|
|
yanchor="bottom", # Position the color scale at the bottom
|
|
),
|
|
hoverinfo='z+x+y'
|
|
))
|
|
|
|
# Set the aspect ratio to ensure square boxes
|
|
fig.update_yaxes(scaleanchor="x", scaleratio=1, constrain="domain")
|
|
|
|
# Update the layout with the axes
|
|
fig.update_layout(
|
|
title=f'Calendar Heatmap for {year}',
|
|
yaxis=dict(
|
|
tickmode='array',
|
|
tickvals=[0, 1, 2, 3, 4, 5, 6],
|
|
ticktext=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
|
|
range=[-1.2, 6.5], # Adjust the range to eliminate space
|
|
automargin=True, # Adjust margins automatically
|
|
domain=[0.1,0.95] # Use available space effectively
|
|
),
|
|
xaxis=dict(
|
|
tickmode='array',
|
|
tickvals=list(range(1, 54)),
|
|
ticktext=list(range(1, 54)),
|
|
title=dict(
|
|
text='Weeks of the Year',
|
|
standoff=0
|
|
),
|
|
title_standoff=0, # Adjust the distance of the title from the axis
|
|
tickangle=-90, # Adjust the angle of the week numbers
|
|
ticklen=2,
|
|
side='top', # Display x-axis ticks above the heatmap
|
|
automargin=True, # Adjust margins automatically
|
|
),
|
|
autosize=True,
|
|
margin=dict(
|
|
l=50,
|
|
r=50,
|
|
b=30, # Increase bottom margin to fit month names
|
|
t=30, # Increase top margin to fit week numbers
|
|
pad=0
|
|
),
|
|
height=450, # Fixed height to avoid excessive space
|
|
)
|
|
|
|
# Add month annotations with arrows at the bottom
|
|
month_starts = df_year.iloc[:-10].groupby('Month')['WeekOfYear'].min().to_dict()
|
|
for month, week in month_starts.items():
|
|
fig.add_annotation(
|
|
x=week,
|
|
y=-0.5, # Position below the x-axis
|
|
text=month,
|
|
showarrow=False,
|
|
arrowhead=2,
|
|
ax=week,
|
|
ay=0, # Position the arrow closer
|
|
xref="x",
|
|
yref="y",
|
|
#font=dict(size=12, color="black"),
|
|
xanchor='left',
|
|
yanchor='top'
|
|
)
|
|
|
|
return fig
|
|
|
|
|
|
def save_load():
|
|
st.session_state['options'].pop("New")
|
|
save_data(settings_file, st.session_state['options'])
|
|
st.session_state['options'] = load_data(settings_file)
|
|
|
|
def update_params():
|
|
st.session_state['cal_url'] = st.session_state['options'][st.session_state['setup']]['cal_url']
|
|
st.session_state['number_of_years'] = st.session_state['options'][st.session_state['setup']]['number_of_years']
|
|
|
|
|
|
# set webpage title
|
|
st.title("Yearly Activity Heatmaps")
|
|
|
|
# File path for data storage
|
|
settings_file = 'settings.json'
|
|
# Load persisted data if it exists; set default values otherwise
|
|
st.session_state['options'] = load_data(settings_file)
|
|
st.session_state['options']["New"] = {
|
|
'cal_url':"https://canada-holidays.ca/ics?cd=true",
|
|
'number_of_years': 3
|
|
}
|
|
|
|
# Get the Calendar Export link and number of years from sidebar menu
|
|
st.sidebar.header("Settings")
|
|
st.session_state['setup'] = st.sidebar.selectbox("Choose a Setup:", st.session_state['options'].keys())
|
|
st.session_state['cal_url'] = st.sidebar.text_input("Enter The Calendar Export Link:", value=st.session_state['options'][st.session_state['setup']]['cal_url'])
|
|
st.session_state['number_of_years'] = st.sidebar.number_input("Enter the number of years:", min_value=1, max_value=10, value=st.session_state['options'][st.session_state['setup']]['number_of_years'])
|
|
update_params()
|
|
if(st.session_state['setup'] == "New"):
|
|
setup_save_name = st.sidebar.text_input("Setup Save Name:", value='yahoo')
|
|
st.session_state['options'][setup_save_name] = {
|
|
'cal_url': st.session_state['cal_url'],
|
|
'number_of_years': st.session_state['number_of_years']
|
|
}
|
|
st.sidebar.button("Save",on_click=save_load)
|
|
|
|
|
|
response = requests.get(st.session_state['cal_url'])
|
|
if response.status_code == 200:
|
|
# Parse the .ics file
|
|
cal = vobject.readOne(response.text)
|
|
else:
|
|
print("Failed to download the file")
|
|
|
|
|
|
|
|
|
|
# Create a dictionary to store event counts by date
|
|
event_counts = {}
|
|
for component in cal.components():
|
|
if component.name == "VEVENT":
|
|
event_date = component.dtstart.value
|
|
if isinstance(event_date, datetime):
|
|
event_date = event_date.date()
|
|
if event_date in event_counts:
|
|
event_counts[event_date] += 1
|
|
else:
|
|
event_counts[event_date] = 1
|
|
|
|
|
|
|
|
# Convert the dictionary to a pandas Series
|
|
event_series = pd.Series(event_counts)
|
|
# Ensure the index is in datetime format
|
|
event_series.index = pd.to_datetime(event_series.index)
|
|
|
|
|
|
# Get the current year
|
|
current_year = datetime.now().year
|
|
# Create a list of years to plot
|
|
years_to_plot = [current_year - i for i in range(st.session_state['number_of_years'])]
|
|
|
|
|
|
|
|
# Plot each year's data
|
|
for year in years_to_plot:
|
|
fig = create_calendar_heatmap(event_series, year)
|
|
st.plotly_chart(fig)
|
|
|
|
|
|
|