elon_py/pkg/dash/func/render.py
2025-03-21 11:57:34 +08:00

146 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 todays 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 todays 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