Compare commits
No commits in common. "ff0a646c81877c20cbc17ceb6991692086051042" and "8a4aa2c0c088e1aa0984786690c2b8fa2570baeb" have entirely different histories.
ff0a646c81
...
8a4aa2c0c0
@ -63,36 +63,8 @@ def layout_config(app):
|
|||||||
'whiteSpace': 'nowrap'
|
'whiteSpace': 'nowrap'
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
# 新增按钮和提示框
|
|
||||||
html.Div(
|
|
||||||
id='info-button',
|
|
||||||
children='ℹ️',
|
|
||||||
style={
|
|
||||||
'fontSize': '24px',
|
|
||||||
'cursor': 'pointer',
|
|
||||||
'padding': '5px',
|
|
||||||
'marginTop': '10px'
|
|
||||||
}
|
|
||||||
),
|
|
||||||
html.Div(
|
|
||||||
id='info-tooltip',
|
|
||||||
children='这是一个信息按钮示例', # 默认显示的信息
|
|
||||||
style={
|
|
||||||
'position': 'absolute',
|
|
||||||
'left': '35px',
|
|
||||||
'top': '80px', # 调整位置,避免与其他 tooltip 重叠
|
|
||||||
'backgroundColor': 'rgba(0, 0, 0, 0.8)',
|
|
||||||
'color': 'white',
|
|
||||||
'padding': '10px',
|
|
||||||
'borderRadius': '5px',
|
|
||||||
'fontSize': '14px',
|
|
||||||
'display': 'none',
|
|
||||||
'whiteSpace': 'nowrap'
|
|
||||||
}
|
|
||||||
),
|
|
||||||
html.A(
|
html.A(
|
||||||
href='https://x.com/elonmusk',
|
href='https://x.com/elonmusk',
|
||||||
target='_blank',
|
|
||||||
children=[
|
children=[
|
||||||
html.Img(
|
html.Img(
|
||||||
src='https://pbs.twimg.com/profile_images/1893803697185910784/Na5lOWi5_400x400.jpg',
|
src='https://pbs.twimg.com/profile_images/1893803697185910784/Na5lOWi5_400x400.jpg',
|
||||||
|
@ -21,3 +21,4 @@ for sub_dir in sub_dirs:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
import pytz
|
|
||||||
from pkg.tool import get_tweets_since_last_friday, format_time_str, get_time_since_last_tweet
|
|
||||||
from pkg.dash.app_init import app
|
|
||||||
from dash.dependencies import Input, Output
|
|
||||||
from datetime import timedelta
|
|
||||||
from datetime import datetime
|
|
||||||
from dash import html
|
|
||||||
|
|
||||||
|
|
||||||
@app.callback(
|
|
||||||
[Output('info-tooltip', 'children')],
|
|
||||||
[Input('clock-interval', 'n_intervals')]
|
|
||||||
)
|
|
||||||
def update_info(n):
|
|
||||||
# 获取所有指标
|
|
||||||
pace = calculate_tweet_pace()
|
|
||||||
decline_rates = calculate_pace_decline_rate()
|
|
||||||
pace_increases = calculate_pace_increase_in_hour()
|
|
||||||
_, days_to_next_friday = get_pace_params()
|
|
||||||
table_style_border = {'textAlign': 'center', 'border': '1px solid white'}
|
|
||||||
table_style_c = {'textAlign': 'center'}
|
|
||||||
table_style_l = {'textAlign': 'left'}
|
|
||||||
|
|
||||||
# First table for Pace
|
|
||||||
pace_table_rows = [
|
|
||||||
html.Tr([
|
|
||||||
html.Th('Pace', style=table_style_border),
|
|
||||||
html.Th('DCL', style=table_style_border),
|
|
||||||
html.Th('0(1h)', style=table_style_border),
|
|
||||||
html.Th('1(1h)', style=table_style_border),
|
|
||||||
html.Th('5(1h)', style=table_style_border),
|
|
||||||
html.Th('10(1h)', style=table_style_border),
|
|
||||||
html.Th('20(1h)', style=table_style_border),
|
|
||||||
html.Th('30(1h)', style=table_style_border)
|
|
||||||
]),
|
|
||||||
html.Tr([
|
|
||||||
html.Td(f"{pace:.2f}", style=table_style_border),
|
|
||||||
html.Td(decline_rates, style=table_style_border),
|
|
||||||
html.Td(f"{pace_increases['increase_0']:.2f}", style=table_style_border),
|
|
||||||
html.Td(f"{pace_increases['increase_1']:.2f}", style=table_style_border),
|
|
||||||
html.Td(f"{pace_increases['increase_5']:.2f}", style=table_style_border),
|
|
||||||
html.Td(f"{pace_increases['increase_10']:.2f}", style=table_style_border),
|
|
||||||
html.Td(f"{pace_increases['increase_20']:.2f}", style=table_style_border),
|
|
||||||
html.Td(f"{pace_increases['increase_30']:.2f}", style=table_style_border)
|
|
||||||
]),
|
|
||||||
html.Tr([
|
|
||||||
html.Td(f"Remain",
|
|
||||||
colSpan=4,
|
|
||||||
style=table_style_c),
|
|
||||||
html.Td(format_time_str(days_to_next_friday),
|
|
||||||
colSpan=4,
|
|
||||||
style=table_style_l)
|
|
||||||
]),
|
|
||||||
html.Tr([
|
|
||||||
html.Td(f"Last Tweet",
|
|
||||||
colSpan=4,
|
|
||||||
style=table_style_c),
|
|
||||||
html.Td(format_time_str(get_time_since_last_tweet()),
|
|
||||||
colSpan=4,
|
|
||||||
style=table_style_l)
|
|
||||||
]),
|
|
||||||
html.Tr([
|
|
||||||
html.Td(f"Warning:",
|
|
||||||
colSpan=1,
|
|
||||||
style=table_style_c),
|
|
||||||
html.Td("1.Check Reply",
|
|
||||||
colSpan=7,
|
|
||||||
style=table_style_l)
|
|
||||||
])
|
|
||||||
]
|
|
||||||
pace_table = html.Table(pace_table_rows, style={
|
|
||||||
'width': '100%',
|
|
||||||
'textAlign': 'left',
|
|
||||||
'borderCollapse': 'collapse'
|
|
||||||
})
|
|
||||||
return [pace_table]
|
|
||||||
|
|
||||||
|
|
||||||
def get_pace_params():
|
|
||||||
est = pytz.timezone('US/Eastern')
|
|
||||||
now = datetime.now(est)
|
|
||||||
today = now.date()
|
|
||||||
days_to_next_friday = (4 - today.weekday()) % 7
|
|
||||||
next_friday = (now.replace(hour=12, minute=0, second=0, microsecond=0) +
|
|
||||||
timedelta(days=days_to_next_friday))
|
|
||||||
if now > next_friday:
|
|
||||||
next_friday += timedelta(days=7)
|
|
||||||
days_to_next_friday = (next_friday - now).total_seconds() / (24 * 60 * 60)
|
|
||||||
tweet_count = get_tweets_since_last_friday()
|
|
||||||
return tweet_count, days_to_next_friday
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_tweet_pace():
|
|
||||||
tweet_count, days_to_next_friday = get_pace_params()
|
|
||||||
pace = (tweet_count / (7 - days_to_next_friday)) * days_to_next_friday + tweet_count
|
|
||||||
return round(pace, 6) if pace > 0 else float(tweet_count)
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_pace_decline_rate():
|
|
||||||
tweet_count, days_to_next_friday = get_pace_params()
|
|
||||||
T = 7
|
|
||||||
decline_per_day = -(tweet_count * T) / ((T - days_to_next_friday) ** 2)
|
|
||||||
decline_per_hour = decline_per_day / 24
|
|
||||||
return round(decline_per_hour, 2)
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_pace_increase_in_hour():
|
|
||||||
tweet_count, days_to_next_friday = get_pace_params()
|
|
||||||
current_pace = (tweet_count / (7 - days_to_next_friday)) * days_to_next_friday + tweet_count
|
|
||||||
future_days = days_to_next_friday - (1 / 24)
|
|
||||||
increments = [0, 1, 5, 10, 20, 30]
|
|
||||||
pace_increases = {}
|
|
||||||
for increment in increments:
|
|
||||||
new_tweet_count = tweet_count + increment
|
|
||||||
new_pace = (new_tweet_count / (7 - future_days)) * future_days + new_tweet_count
|
|
||||||
pace_increase = new_pace - current_pace
|
|
||||||
# Add current pace to the increase value
|
|
||||||
pace_increases[f'increase_{increment}'] = round(current_pace + pace_increase, 2)
|
|
||||||
return pace_increases
|
|
@ -2,7 +2,7 @@ from datetime import datetime, timedelta
|
|||||||
from dash.dependencies import Input, Output
|
from dash.dependencies import Input, Output
|
||||||
from pkg.dash.app_init import app
|
from pkg.dash.app_init import app
|
||||||
from pkg.config import render_data
|
from pkg.config import render_data
|
||||||
from pkg.tool import aggregate_data, generate_xticks, minutes_to_time, get_tweets_since_last_friday
|
from pkg.tool import aggregate_data, generate_xticks, minutes_to_time
|
||||||
from dash import dcc
|
from dash import dcc
|
||||||
import plotly.graph_objs as go
|
import plotly.graph_objs as go
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -137,5 +137,5 @@ def render_tab_content(tab, selected_dates, interval, time_zones, days_to_displa
|
|||||||
yaxis=dict(autorange='reversed') if tab == 'one_day_heatmap' else None
|
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()}"
|
summary = f"Total tweets for selected dates: {int(tweet_count_total)}"
|
||||||
return dcc.Graph(figure=fig), warning, summary
|
return dcc.Graph(figure=fig), warning, summary
|
@ -1,35 +1,18 @@
|
|||||||
from dash.dependencies import Input, Output, State
|
from dash.dependencies import Input, Output, State
|
||||||
from dash import clientside_callback
|
from dash import clientside_callback
|
||||||
|
|
||||||
|
|
||||||
def setting_callback():
|
def setting_callback():
|
||||||
clientside_callback(
|
clientside_callback(
|
||||||
"""
|
"""
|
||||||
function(n_intervals) {
|
function(n_intervals) {
|
||||||
const clockButton = document.getElementById('clock-button');
|
const button = document.getElementById('clock-button');
|
||||||
const clockTooltip = document.getElementById('clock-tooltip');
|
const tooltip = document.getElementById('clock-tooltip');
|
||||||
if (clockButton && clockTooltip) {
|
if (button && tooltip) {
|
||||||
clockButton.addEventListener('mouseover', () => {
|
button.addEventListener('mouseover', () => {
|
||||||
clockTooltip.style.display = 'block';
|
tooltip.style.display = 'block';
|
||||||
});
|
});
|
||||||
clockButton.addEventListener('mouseout', () => {
|
button.addEventListener('mouseout', () => {
|
||||||
clockTooltip.style.display = 'none';
|
tooltip.style.display = 'none';
|
||||||
});
|
|
||||||
}
|
|
||||||
const infoButton = document.getElementById('info-button');
|
|
||||||
const infoTooltip = document.getElementById('info-tooltip');
|
|
||||||
if (infoButton && infoTooltip) {
|
|
||||||
infoButton.addEventListener('mouseover', () => {
|
|
||||||
infoTooltip.style.display = 'block';
|
|
||||||
});
|
|
||||||
infoButton.addEventListener('mouseout', () => {
|
|
||||||
infoTooltip.style.display = 'none';
|
|
||||||
});
|
|
||||||
infoTooltip.addEventListener('mouseover', () => {
|
|
||||||
infoTooltip.style.display = 'block';
|
|
||||||
});
|
|
||||||
infoTooltip.addEventListener('mouseout', () => {
|
|
||||||
infoTooltip.style.display = 'none';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return window.dash_clientside.no_update;
|
return window.dash_clientside.no_update;
|
||||||
|
51
pkg/tool.py
51
pkg/tool.py
@ -1,7 +1,5 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from pkg.config import render_data
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
def aggregate_data(data, interval):
|
def aggregate_data(data, interval):
|
||||||
all_minutes = pd.DataFrame({'interval_group': range(0, 1440, interval)})
|
all_minutes = pd.DataFrame({'interval_group': range(0, 1440, interval)})
|
||||||
@ -48,50 +46,3 @@ def minutes_to_time(minutes):
|
|||||||
hours = minutes // 60
|
hours = minutes // 60
|
||||||
mins = minutes % 60
|
mins = minutes % 60
|
||||||
return f"{hours:02d}:{mins:02d}"
|
return f"{hours:02d}:{mins:02d}"
|
||||||
|
|
||||||
|
|
||||||
def get_tweets_since_last_friday():
|
|
||||||
est = pytz.timezone('US/Eastern')
|
|
||||||
now_est = datetime.now(est)
|
|
||||||
today = now_est.date()
|
|
||||||
days_since_friday = (today.weekday() - 4) % 7
|
|
||||||
this_friday = today - timedelta(days=days_since_friday)
|
|
||||||
this_friday_datetime = est.localize(datetime.combine(this_friday, datetime.strptime("12:00", "%H:%M").time()))
|
|
||||||
last_friday = this_friday - timedelta(days=7)
|
|
||||||
last_friday_datetime = est.localize(datetime.combine(last_friday, datetime.strptime("12:00", "%H:%M").time()))
|
|
||||||
if now_est < this_friday_datetime:
|
|
||||||
start_datetime = last_friday_datetime
|
|
||||||
else:
|
|
||||||
start_datetime = this_friday_datetime
|
|
||||||
if hasattr(render_data, 'global_df') and not render_data.global_df.empty:
|
|
||||||
df = render_data.global_df.copy()
|
|
||||||
mask = df['datetime_est'] >= start_datetime
|
|
||||||
filtered_df = df[mask]
|
|
||||||
tweet_count = len(filtered_df)
|
|
||||||
return int(tweet_count)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def get_time_since_last_tweet():
|
|
||||||
est = pytz.timezone('US/Eastern')
|
|
||||||
now_est = datetime.now(est)
|
|
||||||
if (not hasattr(render_data, 'global_df') or
|
|
||||||
render_data.global_df is None or
|
|
||||||
render_data.global_df.empty):
|
|
||||||
return 0.0
|
|
||||||
df = render_data.global_df
|
|
||||||
if 'datetime_est' not in df.columns:
|
|
||||||
return 0.0
|
|
||||||
latest_tweet_time = df['datetime_est'].max()
|
|
||||||
time_diff = now_est - latest_tweet_time
|
|
||||||
days_diff = time_diff.total_seconds() / (24 * 60 * 60) # 转换为天数
|
|
||||||
return days_diff
|
|
||||||
|
|
||||||
def format_time_str(days_to_next_friday):
|
|
||||||
total_seconds = days_to_next_friday * 24 * 60 * 60
|
|
||||||
days = int(total_seconds // (24 * 60 * 60))
|
|
||||||
hours = int((total_seconds % (24 * 60 * 60)) // (60 * 60))
|
|
||||||
minutes = int((total_seconds % (60 * 60)) // 60)
|
|
||||||
seconds = int(total_seconds % 60)
|
|
||||||
total_hours = round(days_to_next_friday * 24, 2)
|
|
||||||
return f"{days}d {hours:02d}h {minutes:02d}m {seconds:02d}s ({total_hours}h)"
|
|
Loading…
x
Reference in New Issue
Block a user