import dash from dash import dcc, html from dash.dependencies import Input, Output import plotly.graph_objs as go import pandas as pd import pytz from datetime import datetime, timedelta from sqlalchemy import create_engine from dash import clientside_callback # Database connection configuration (unchanged) DB_CONFIG = { 'host': '8.155.23.172', 'port': 3306, 'user': 'root2', 'password': 'tG0f6PVYh18le41BCb', 'database': 'elonX' } TABLE_NAME = 'elon_tweets' db_uri = f"mysql+pymysql://{DB_CONFIG['user']}:{DB_CONFIG['password']}@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}" engine = create_engine(db_uri) # Load data (unchanged) df = pd.read_sql(f'SELECT timestamp FROM {TABLE_NAME}', con=engine) eastern = pytz.timezone('America/New_York') pacific = pytz.timezone('America/Los_Angeles') central = pytz.timezone('America/Chicago') df['datetime'] = pd.to_datetime(df['timestamp'], unit='s') df['datetime_est'] = df['datetime'].dt.tz_localize('UTC').dt.tz_convert(eastern) df['date'] = df['datetime_est'].dt.date df['minute_of_day'] = df['datetime_est'].dt.hour * 60 + df['datetime_est'].dt.minute agg_df = df.groupby(['date', 'minute_of_day']).size().reset_index(name='tweet_count') all_dates = sorted(agg_df['date'].unique(), reverse=True) default_date = [str(all_dates[0])] # Initialize Dash app (unchanged) external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] app = dash.Dash(__name__, external_stylesheets=external_stylesheets) # Time interval and days options (Modified: removed 1 day, added 120 and 240 days) interval_options = [ {'label': '1 minute', 'value': 1}, {'label': '5 minutes', 'value': 5}, {'label': '10 minutes', 'value': 10}, {'label': '30 minutes', 'value': 30}, {'label': '60 minutes', 'value': 60} ] days_options = [ {'label': '7 days', 'value': 7}, {'label': '30 days', 'value': 30}, {'label': '90 days', 'value': 90}, {'label': '120 days', 'value': 120}, {'label': '240 days', 'value': 240} ] # Dash app layout (unchanged except default days value) app.layout = html.Div([ # Left sidebar with clock button and tooltip (unchanged) html.Div( id='clock-container', children=[ html.Div( id='clock-button', children='🕒', style={ 'fontSize': '24px', 'cursor': 'pointer', 'padding': '5px', } ), html.Div( id='clock-tooltip', children=[ html.Div(id='pst-clock'), html.Div(id='cst-clock'), html.Div(id='est-clock') ], style={ 'position': 'absolute', 'left': '35px', 'top': '0px', 'backgroundColor': 'rgba(0, 0, 0, 0.8)', 'color': 'white', 'padding': '10px', 'borderRadius': '5px', 'fontSize': '14px', 'display': 'none', 'whiteSpace': 'nowrap' } ) ], style={ 'position': 'fixed', 'left': '10px', 'top': '50%', 'transform': 'translateY(-50%)', 'zIndex': 1000 } ), # Main content html.Div([ html.H1("Elon Musk Tweet Time Analysis (EST)"), html.Div(id='date-picker-container', children=[ dcc.Dropdown( id='multi-date-picker', options=[{'label': str(date), 'value': str(date)} for date in all_dates], value=default_date, multi=True, searchable=True, placeholder="Search and select dates (YYYY-MM-DD)", style={'width': '100%'} ) ]), dcc.Dropdown( id='multi-interval-picker', options=interval_options, value=10, style={'width': '50%', 'marginTop': '10px'} ), html.Div(id='days-display-container', style={'display': 'none'}, children=[ dcc.Dropdown( id='days-display-picker', options=days_options, value=30, # Default changed to 30 since 1 is removed style={'width': '50%', 'marginTop': '10px'} ) ]), html.Div(id='multi-day-warning', style={'color': 'red', 'margin': '10px'}), dcc.Checklist( id='time-zone-checklist', options=[ {'label': 'California Time (PST)', 'value': 'PST'}, {'label': 'Texas Time (CST)', 'value': 'CST'} ], value=['PST'], style={'margin': '10px'} ), html.Div(id='multi-tweet-summary', style={'fontSize': '20px', 'margin': '10px'}), dcc.Tabs(id='tabs', value='line', children=[ dcc.Tab(label='Line Chart', value='line'), dcc.Tab(label='Heatmap', value='heatmap'), dcc.Tab(label='Scatter Plot', value='scatter'), ]), html.Div(id='tabs-content'), ], style={'marginLeft': '50px'}), dcc.Interval(id='clock-interval', interval=1000, n_intervals=0) ]) # Clientside callback (unchanged) clientside_callback( """ function(n_intervals) { const container = document.getElementById('clock-container'); const tooltip = document.getElementById('clock-tooltip'); if (container && tooltip) { container.addEventListener('mouseover', () => { tooltip.style.display = 'block'; }); container.addEventListener('mouseout', () => { tooltip.style.display = 'none'; }); } return window.dash_clientside.no_update; } """, Output('clock-container', 'id'), Input('clock-interval', 'n_intervals'), prevent_initial_call=False ) # Auxiliary functions (unchanged) def aggregate_data(data, interval): all_minutes = pd.DataFrame({'interval_group': range(0, 1440, interval)}) result = [] for date in data['date'].unique(): day_data = data[data['date'] == date].copy() day_data['interval_group'] = (day_data['minute_of_day'] // interval) * interval agg = day_data.groupby('interval_group').size().reset_index(name='tweet_count') complete_data = all_minutes.merge(agg, on='interval_group', how='left').fillna({'tweet_count': 0}) complete_data['date'] = date result.append(complete_data) return pd.concat(result, ignore_index=True) def generate_xticks(interval): if interval <= 5: tick_step = 60 elif interval <= 10: tick_step = 60 elif interval <= 30: tick_step = 120 else: tick_step = 240 ticks = list(range(0, 1440, tick_step)) tick_labels = [f"{m // 60:02d}:{m % 60:02d}" for m in ticks] return ticks, tick_labels def minutes_to_time(minutes): hours = minutes // 60 mins = minutes % 60 return f"{hours:02d}:{mins:02d}" # Callback for updating clocks (unchanged) @app.callback( [Output('pst-clock', 'children'), Output('cst-clock', 'children'), Output('est-clock', 'children')], [Input('clock-interval', 'n_intervals')] ) def update_clocks(n): now_utc = datetime.now(pytz.UTC) pst_time = now_utc.astimezone(pacific).strftime('%Y-%m-%d %H:%M:%S PST') cst_time = now_utc.astimezone(central).strftime('%Y-%m-%d %H:%M:%S CST') est_time = now_utc.astimezone(eastern).strftime('%Y-%m-%d %H:%M:%S EST') return f"🏛️/🌴: {pst_time}", f"🚀: {cst_time}", f"𝕏: {est_time}" # Callback for toggling controls visibility (unchanged) @app.callback( [Output('date-picker-container', 'style'), Output('days-display-container', 'style')], [Input('tabs', 'value')] ) def toggle_controls_visibility(tab): if tab == 'heatmap': return {'display': 'none'}, {'display': 'block'} return {'display': 'block'}, {'display': 'none'} # Callback for updating tabs content (Modified to add Thursday-Friday lines) @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 != 'heatmap': 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: selected_dates = sorted(all_dates, reverse=True)[:days_to_display] multi_data_agg = agg_df[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 = 0 else: tweet_count_total = multi_data_agg['tweet_count'].sum() multi_data_raw = df[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 == 'scatter': fig = go.Figure() for date in selected_dates: day_data = multi_data_raw[multi_data_raw['date'] == date] if not day_data.empty: hover_times = [t.strftime('%Y-%m-%d %H:%M:%S EST') for t in day_data['datetime_est']] fig.add_trace(go.Scatter( x=day_data['minute_of_day'], y=[str(date)] * len(day_data), mode='markers', name=str(date), customdata=hover_times, hovertemplate='%{customdata}', marker=dict(size=8) )) if tab in ['line', 'scatter']: if 'PST' in time_zones: pacific_2am_est = (2 + 3) * 60 pacific_7am_est = (7 + 3) * 60 fig.add_vline(x=pacific_2am_est, line_dash="dash", line_color="blue", annotation_text="CA 2AM PST") fig.add_vline(x=pacific_7am_est, line_dash="dash", line_color="blue", annotation_text="CA 7AM PST") if 'CST' in time_zones: central_2am_est = (2 + 1) * 60 central_7am_est = (7 + 1) * 60 fig.add_vline(x=central_2am_est, line_dash="dash", line_color="green", annotation_text="TX 2AM CST") fig.add_vline(x=central_7am_est, line_dash="dash", line_color="green", annotation_text="TX 7AM CST") if tab in ['line', 'scatter']: fig.update_layout( title=f'{"Line" if tab == "line" else "Scatter"} Tweet Frequency (Interval: {interval} minutes, EST)', xaxis_title='Eastern Time (HH:MM)', yaxis_title='Tweet Count' if tab == 'line' else 'Date', xaxis=dict(range=[0, 1440], tickvals=xticks, ticktext=xtick_labels, tickangle=45), height=600, showlegend=True, yaxis=dict(autorange='reversed') if tab == 'scatter' else None ) summary = f"Total tweets for selected dates: {int(tweet_count_total)}" return dcc.Graph(figure=fig), warning, summary # Run the app if __name__ == '__main__': app.run_server(debug=True)