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}
Tweets: %{y}' )) 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
Tweets: %{z}' )) 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
Tweets: %{z}
Rate: %{customdata:.2%}' )) 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 for selected dates: {int(tweet_count_total)}Total tweets: {get_tweets_since_last_friday()}" return dcc.Graph(figure=fig), warning, summary