146 lines
6.3 KiB
Python
146 lines
6.3 KiB
Python
from datetime import datetime, timedelta
|
||
from dash.dependencies import Input, Output
|
||
from pkg.dash.app_init import app
|
||
from pkg.config import render_data
|
||
from pkg.tool import aggregate_data, generate_xticks, minutes_to_time, get_tweets_since_last_friday
|
||
from dash import dcc
|
||
import plotly.graph_objs as go
|
||
import pandas as pd
|
||
|
||
|
||
@app.callback(
|
||
[Output('tabs-content', 'children'),
|
||
Output('multi-day-warning', 'children'),
|
||
Output('multi-tweet-summary', 'children')],
|
||
[Input('tabs', 'value'),
|
||
Input('multi-date-picker', 'value'),
|
||
Input('multi-interval-picker', 'value'),
|
||
Input('time-zone-checklist', 'value'),
|
||
Input('days-display-picker', 'value')]
|
||
)
|
||
def render_tab_content(tab, selected_dates, interval, time_zones, days_to_display):
|
||
warning = ""
|
||
if tab == 'line':
|
||
if not selected_dates: # Handle None or empty list
|
||
selected_dates = [datetime.now().date()] # Default to today
|
||
warning = "No dates selected. Showing today’s data."
|
||
if len(selected_dates) > 10:
|
||
selected_dates = selected_dates[:10]
|
||
warning = "Maximum of 10 days can be selected. Showing first 10 selected days."
|
||
selected_dates = [datetime.strptime(date, '%Y-%m-%d').date() for date in selected_dates]
|
||
else:
|
||
available_dates = sorted(render_data.global_agg_df['date'].unique(), reverse=True)
|
||
selected_dates = available_dates[:days_to_display] if available_dates else [datetime.now().date()]
|
||
if not available_dates:
|
||
warning = "No data available. Showing today’s date with zero tweets."
|
||
|
||
multi_data_agg = render_data.global_agg_df[render_data.global_agg_df['date'].isin(selected_dates)].copy()
|
||
if multi_data_agg.empty:
|
||
multi_data_agg = pd.DataFrame({
|
||
'date': selected_dates,
|
||
'minute_of_day': [0] * len(selected_dates),
|
||
})
|
||
tweet_count_total = multi_data_agg.get('tweet_count', pd.Series([0] * len(multi_data_agg))).sum()
|
||
|
||
multi_data_raw = render_data.global_df[render_data.global_df['date'].isin(selected_dates)].copy()
|
||
if multi_data_raw.empty:
|
||
tweet_count_total = 0
|
||
|
||
agg_data = aggregate_data(multi_data_agg, interval)
|
||
xticks, xtick_labels = generate_xticks(interval)
|
||
|
||
if tab == 'line':
|
||
fig = go.Figure()
|
||
for date in selected_dates:
|
||
day_data = agg_data[agg_data['date'] == date]
|
||
hover_times = [f"{date} {minutes_to_time(minute)} EST" for minute in day_data['interval_group']]
|
||
fig.add_trace(go.Scatter(
|
||
x=day_data['interval_group'],
|
||
y=day_data['tweet_count'],
|
||
mode='lines',
|
||
name=str(date),
|
||
customdata=hover_times,
|
||
hovertemplate='%{customdata}<br>Tweets: %{y}<extra></extra>'
|
||
))
|
||
|
||
elif tab == 'heatmap':
|
||
pivot_data = agg_data.pivot(index='date', columns='interval_group', values='tweet_count').fillna(0)
|
||
pivot_data.index = pivot_data.index.astype(str)
|
||
fig = go.Figure(data=go.Heatmap(
|
||
z=pivot_data.values,
|
||
x=[minutes_to_time(m) for m in pivot_data.columns],
|
||
y=pivot_data.index,
|
||
colorscale='Viridis',
|
||
hoverongaps=False,
|
||
hovertemplate='%{y} %{x} EST<br>Tweets: %{z}<extra></extra>'
|
||
))
|
||
|
||
for i, date_str in enumerate(pivot_data.index):
|
||
date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||
if date.weekday() == 4: # Friday
|
||
prev_date = date - timedelta(days=1)
|
||
if str(prev_date) in pivot_data.index:
|
||
y_position = i / len(pivot_data.index)
|
||
fig.add_hline(
|
||
y=1 - y_position,
|
||
line_dash="dash",
|
||
line_color="white",
|
||
xref="x",
|
||
yref="paper"
|
||
)
|
||
fig.update_layout(
|
||
title=f'Tweet Heatmap (Interval: {interval} minutes, EST, {len(selected_dates)} days)',
|
||
xaxis_title='Time of Day (HH:MM EST)',
|
||
yaxis_title='Date',
|
||
height=max(400, len(selected_dates) * 20),
|
||
yaxis=dict(autorange='reversed')
|
||
)
|
||
|
||
elif tab == 'one_day_heatmap':
|
||
one_day_data = agg_data.groupby('interval_group')['tweet_count'].sum().reset_index()
|
||
|
||
hours = list(range(24))
|
||
intervals_per_hour = 60 // interval
|
||
z_values = [[0] * intervals_per_hour for _ in range(24)]
|
||
|
||
for _, row in one_day_data.iterrows():
|
||
minute = row['interval_group']
|
||
hour = int(minute // 60) # Convert to integer
|
||
interval_idx = int((minute % 60) // interval) # Convert to integer
|
||
if hour < 24:
|
||
z_values[hour][interval_idx] = row['tweet_count']
|
||
|
||
x_labels = [f"{i * interval:02d}" for i in range(intervals_per_hour)]
|
||
|
||
rate_values = [[z_values[h][i] / tweet_count_total if tweet_count_total > 0 else 0
|
||
for i in range(intervals_per_hour)]
|
||
for h in range(24)]
|
||
|
||
fig = go.Figure(data=go.Heatmap(
|
||
z=z_values,
|
||
x=x_labels,
|
||
y=[f"{h:02d}" for h in hours],
|
||
colorscale='Viridis',
|
||
hoverongaps=False,
|
||
customdata=rate_values,
|
||
hovertemplate='%{y}:%{x} EST<br>Tweets: %{z}<br>Rate: %{customdata:.2%}<extra></extra>'
|
||
))
|
||
|
||
if tab in ['line', 'one_day_heatmap']:
|
||
fig.update_layout(
|
||
title=f'{"Line" if tab == "line" else "One-Day Heatmap"} Tweet Frequency (Interval: {interval} minutes, EST, {len(selected_dates)} days)',
|
||
xaxis_title='Minutes' if tab == 'one_day_heatmap' else 'Eastern Time (HH:MM)',
|
||
yaxis_title='Hour of Day' if tab == 'one_day_heatmap' else 'Tweet Count',
|
||
xaxis=dict(
|
||
range=[0, 1440] if tab == 'line' else None,
|
||
tickvals=xticks if tab == 'line' else None,
|
||
ticktext=xtick_labels if tab == 'line' else None,
|
||
tickangle=45 if tab == 'line' else 0
|
||
),
|
||
height=600,
|
||
showlegend=(tab == 'line'),
|
||
yaxis=dict(autorange='reversed') if tab == 'one_day_heatmap' else None
|
||
)
|
||
|
||
summary = f"Total tweets: {get_tweets_since_last_friday()}"
|
||
return dcc.Graph(figure=fig), warning, summary |