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'
|
'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,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 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
|
from pkg.tool import aggregate_data, generate_xticks, minutes_to_time, get_tweets_since_last_friday
|
||||||
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)}"
|
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
|
return dcc.Graph(figure=fig), warning, summary
|
@ -1,18 +1,35 @@
|
|||||||
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 button = document.getElementById('clock-button');
|
const clockButton = document.getElementById('clock-button');
|
||||||
const tooltip = document.getElementById('clock-tooltip');
|
const clockTooltip = document.getElementById('clock-tooltip');
|
||||||
if (button && tooltip) {
|
if (clockButton && clockTooltip) {
|
||||||
button.addEventListener('mouseover', () => {
|
clockButton.addEventListener('mouseover', () => {
|
||||||
tooltip.style.display = 'block';
|
clockTooltip.style.display = 'block';
|
||||||
});
|
});
|
||||||
button.addEventListener('mouseout', () => {
|
clockButton.addEventListener('mouseout', () => {
|
||||||
tooltip.style.display = 'none';
|
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;
|
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
|
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)})
|
||||||
@ -46,3 +48,50 @@ 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