Compare commits
6 Commits
8a4aa2c0c0
...
ff0a646c81
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ff0a646c81 | ||
![]() |
ab64ec4536 | ||
![]() |
c4534abe52 | ||
![]() |
82f69596b8 | ||
![]() |
a0f436c98f | ||
![]() |
205b1ed311 |
@ -63,8 +63,36 @@ def layout_config(app):
|
||||
'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(
|
||||
href='https://x.com/elonmusk',
|
||||
target='_blank',
|
||||
children=[
|
||||
html.Img(
|
||||
src='https://pbs.twimg.com/profile_images/1893803697185910784/Na5lOWi5_400x400.jpg',
|
||||
|
@ -21,4 +21,3 @@ for sub_dir in sub_dirs:
|
||||
|
||||
|
||||
|
||||
|
||||
|
119
pkg/dash/func/info.py
Normal file
119
pkg/dash/func/info.py
Normal file
@ -0,0 +1,119 @@
|
||||
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 pkg.dash.app_init import app
|
||||
from pkg.config import render_data
|
||||
from pkg.tool import aggregate_data, generate_xticks, minutes_to_time
|
||||
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
|
||||
@ -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
|
||||
)
|
||||
|
||||
summary = f"Total tweets for selected dates: {int(tweet_count_total)}"
|
||||
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
|
@ -1,18 +1,35 @@
|
||||
from dash.dependencies import Input, Output, State
|
||||
from dash import clientside_callback
|
||||
|
||||
|
||||
def setting_callback():
|
||||
clientside_callback(
|
||||
"""
|
||||
function(n_intervals) {
|
||||
const button = document.getElementById('clock-button');
|
||||
const tooltip = document.getElementById('clock-tooltip');
|
||||
if (button && tooltip) {
|
||||
button.addEventListener('mouseover', () => {
|
||||
tooltip.style.display = 'block';
|
||||
const clockButton = document.getElementById('clock-button');
|
||||
const clockTooltip = document.getElementById('clock-tooltip');
|
||||
if (clockButton && clockTooltip) {
|
||||
clockButton.addEventListener('mouseover', () => {
|
||||
clockTooltip.style.display = 'block';
|
||||
});
|
||||
button.addEventListener('mouseout', () => {
|
||||
tooltip.style.display = 'none';
|
||||
clockButton.addEventListener('mouseout', () => {
|
||||
clockTooltip.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;
|
||||
|
51
pkg/tool.py
51
pkg/tool.py
@ -1,5 +1,7 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
from pkg.config import render_data
|
||||
import pytz
|
||||
|
||||
def aggregate_data(data, interval):
|
||||
all_minutes = pd.DataFrame({'interval_group': range(0, 1440, interval)})
|
||||
@ -46,3 +48,50 @@ def minutes_to_time(minutes):
|
||||
hours = minutes // 60
|
||||
mins = minutes % 60
|
||||
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