Spaces:
Sleeping
Sleeping
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| # Sample data for demonstration (full year 2024) | |
| data = { | |
| 'date': pd.date_range('2024-01-01', periods=365, freq='D'), | |
| 'value': np.random.randint(50, 200, 365) | |
| } | |
| df = pd.DataFrame(data) | |
| # Extract calendar components | |
| df['month'] = df['date'].dt.month # 1..12 | |
| # Convert pandas weekday (Monday=0..Sunday=6) to Sun=0..Sat=6 | |
| df['weekday_sun0'] = (df['date'].dt.dayofweek + 1) % 7 | |
| # Aggregate values by month x weekday | |
| agg = ( | |
| df.groupby(['month', 'weekday_sun0'], as_index=False)['value'] | |
| .sum() | |
| .rename(columns={'value': 'total_value'}) | |
| ) | |
| # Ensure all 12x7 cells exist (fill missing with 0) | |
| full = pd.MultiIndex.from_product( | |
| [range(1, 13), range(0, 7)], | |
| names=['month', 'weekday_sun0'] | |
| ).to_frame(index=False) | |
| agg = full.merge(agg, on=['month', 'weekday_sun0'], how='left').fillna({'total_value': 0.0}) | |
| # Labels for months and weekdays | |
| month_labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
| weekday_labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] | |
| agg['month_name'] = agg['month'].map(lambda m: month_labels[m-1]) | |
| agg['weekday_name'] = agg['weekday_sun0'].map(lambda w: weekday_labels[w]) | |
| # Rings: 1..7 (Sun=1 inner → Sat=7 outer) | |
| agg['r'] = agg['weekday_sun0'] + 1 | |
| # Bubble size normalization (log1p compresses large values; then scale to pixel range) | |
| max_marker_px = 42 | |
| min_marker_px = 6 | |
| s = agg['total_value'].to_numpy(dtype=float) | |
| s_log = np.log1p(s) | |
| if np.allclose(s_log.max(), s_log.min()): | |
| agg['size_px'] = min_marker_px | |
| else: | |
| # Scale log values to [min_marker_px, max_marker_px] | |
| scaled = (s_log - s_log.min()) / (s_log.max() - s_log.min()) | |
| agg['size_px'] = min_marker_px + scaled * (max_marker_px - min_marker_px) | |
| # Sizeref for area sizing | |
| sizeref = 2.0 * agg['size_px'].max() / (max_marker_px ** 2) | |
| # Build polar scatter chart | |
| fig = px.scatter_polar( | |
| agg, | |
| r='r', | |
| theta='month_name', | |
| size='size_px', | |
| size_max=max_marker_px, | |
| color='total_value', | |
| color_continuous_scale='Viridis', | |
| hover_data={ | |
| 'month_name': True, | |
| 'weekday_name': True, | |
| 'total_value': ':,.2f', | |
| 'r': False, | |
| 'size_px': False, | |
| 'month': False, | |
| 'weekday_sun0': False, | |
| }, | |
| title='Circular Calendar View - Monthly Values by Weekday (2024)', | |
| ) | |
| # Force area sizing behavior | |
| fig.update_traces(marker=dict(sizemode='area', sizeref=sizeref, line=dict(width=0.6))) | |
| # Clockwise months, start Jan at top | |
| fig.update_layout( | |
| polar=dict( | |
| angularaxis=dict( | |
| direction='clockwise', | |
| rotation=90, # puts Jan at the top | |
| ), | |
| radialaxis=dict( | |
| tickmode='array', | |
| tickvals=list(range(1, 8)), | |
| ticktext=weekday_labels, # Sun..Sat | |
| range=[0.5, 7.5], | |
| showline=False, | |
| gridcolor='rgba(0,0,0,0.12)', | |
| ), | |
| ), | |
| coloraxis_colorbar=dict(title='Value'), | |
| template='plotly_white', | |
| margin=dict(l=40, r=40, t=70, b=40), | |
| height=800, | |
| ) | |
| fig.show() |