-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfix_midnight_display_freeze.py
More file actions
295 lines (223 loc) · 10.4 KB
/
Copy pathfix_midnight_display_freeze.py
File metadata and controls
295 lines (223 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
#!/usr/bin/env python3
"""
Fix for Midnight Display Freeze Bug
This script contains the fix that forces Panel components to recreate
when the date range changes at midnight.
The fix involves:
1. Detecting when date range changes (not just dates)
2. Forcing component recreation when range changes
3. Using param.trigger() to ensure display updates
"""
import logging
logger = logging.getLogger(__name__)
class MidnightDisplayFix:
"""
Mixin class that provides the midnight display fix functionality.
This can be added to dashboard components that need to handle midnight rollovers.
"""
def __init__(self):
# Track the last date range to detect changes
self._last_date_range = None
self._components_to_refresh = []
def register_component_for_refresh(self, component_name, pane):
"""Register a Panel pane that needs refreshing at midnight"""
self._components_to_refresh.append({
'name': component_name,
'pane': pane
})
def check_date_range_change(self, start_date, end_date):
"""
Check if the date range has changed since last update.
Returns True if range changed (requiring component recreation).
"""
current_range = (start_date, end_date)
if self._last_date_range is None:
self._last_date_range = current_range
return False
range_changed = self._last_date_range != current_range
if range_changed:
logger.info(f"Date range changed: {self._last_date_range} → {current_range}")
self._last_date_range = current_range
return range_changed
def force_component_refresh(self):
"""
Force all registered Panel components to refresh their display.
This is the key fix - it ensures Panel updates the display.
"""
logger.info("Forcing component refresh due to date range change")
for component in self._components_to_refresh:
try:
pane = component['pane']
name = component['name']
# Method 1: Trigger param update to force refresh
if hasattr(pane, 'param') and hasattr(pane.param, 'trigger'):
pane.param.trigger('object')
logger.info(f"Triggered refresh for {name} using param.trigger")
# Method 2: If pane has object property, reassign it
elif hasattr(pane, 'object'):
# Force reassignment to trigger update
current_object = pane.object
pane.object = None # Clear first
pane.object = current_object # Reassign
logger.info(f"Forced object reassignment for {name}")
except Exception as e:
logger.error(f"Error refreshing component {name}: {e}")
def create_plot_with_refresh(self, data, plot_func, pane_name):
"""
Create a plot and its pane with automatic refresh capability.
Args:
data: The data to plot
plot_func: Function that creates the plot from data
pane_name: Name for this pane (for logging)
Returns:
The Panel pane containing the plot
"""
import panel as pn
# Create the plot
plot = plot_func(data)
# Create the pane
pane = pn.pane.HoloViews(plot)
# Register for refresh
self.register_component_for_refresh(pane_name, pane)
return pane
def apply_midnight_fix_to_update_loop(dashboard_instance):
"""
Apply the midnight display fix to a dashboard's update loop.
This wraps the existing update logic with date range checking.
"""
# Store original update method
original_update = dashboard_instance.update_plot
# Create fix instance
fix = MidnightDisplayFix()
def wrapped_update():
"""Enhanced update that handles midnight properly"""
# Get current date range
start_date = dashboard_instance.start_date
end_date = dashboard_instance.end_date
# Check if date range changed
if fix.check_date_range_change(start_date, end_date):
logger.info("Date range change detected - forcing component refresh")
# First, update the data as normal
original_update()
# Then force Panel components to refresh
fix.force_component_refresh()
# If dashboard has specific panes, refresh them
if hasattr(dashboard_instance, 'refresh_all_panes'):
dashboard_instance.refresh_all_panes()
else:
# Normal update when date range hasn't changed
original_update()
# Replace update method
dashboard_instance.update_plot = wrapped_update
# Attach fix instance for component registration
dashboard_instance._midnight_fix = fix
logger.info("Midnight display fix applied to dashboard")
return dashboard_instance
def enhanced_auto_update_loop(self):
"""
Enhanced auto_update_loop that properly handles midnight rollovers.
This replaces the existing auto_update_loop in gen_dash.py
"""
import asyncio
from datetime import datetime
async def auto_update_loop():
"""Main update loop with midnight display fix"""
# Track last date range for change detection
last_date_range = None
while True:
try:
# FIX for midnight rollover bug: Refresh dates FIRST
if self.time_range in ['1', '7', '30']:
old_start = self.start_date
old_end = self.end_date
self._update_date_range_from_preset()
new_start = self.start_date
new_end = self.end_date
# Check if date RANGE changed (not just dates)
old_range = (old_start, old_end)
new_range = (new_start, new_end)
if old_range != new_range and last_date_range is not None:
self.logger.info(f"Date range changed: {old_range} → {new_range}")
# CRITICAL FIX: Force Panel component recreation
if hasattr(self, 'recreate_all_plots'):
self.logger.info("Recreating all plots due to date range change")
self.recreate_all_plots()
else:
# Fallback: trigger refresh on all panes
self.logger.info("Forcing component refresh")
self.force_panel_refresh()
last_date_range = new_range
# Normal update
self.update_plot()
# Also update loading indicator
if hasattr(self, 'loading_indicator'):
self.loading_indicator.object = self._create_loading_indicator()
except Exception as e:
self.logger.error(f"Error in auto-update loop: {e}")
# Wait before next update
await asyncio.sleep(self.update_interval)
return auto_update_loop
def create_recreate_all_plots_method(dashboard_instance):
"""
Create a method that recreates all plot objects.
This is the nuclear option but guarantees display updates.
"""
def recreate_all_plots(self):
"""Recreate all plot objects to force display update"""
import panel as pn
try:
# List of plot attributes to recreate
plot_attrs = [
'generation_plot_pane',
'price_plot_pane',
'renewable_gauge_pane',
'transmission_plot_pane'
]
for attr_name in plot_attrs:
if hasattr(self, attr_name):
pane = getattr(self, attr_name)
if isinstance(pane, pn.pane.HoloViews):
# Get current plot object
current_plot = pane.object
# Force recreation by clearing and reassigning
pane.object = None
pane.object = current_plot
self.logger.info(f"Recreated {attr_name}")
except Exception as e:
self.logger.error(f"Error recreating plots: {e}")
# Attach method to instance
dashboard_instance.recreate_all_plots = recreate_all_plots.__get__(dashboard_instance)
def create_force_panel_refresh_method(dashboard_instance):
"""
Create a method that forces Panel to refresh all components.
Uses param.trigger() which is Panel's recommended way.
"""
def force_panel_refresh(self):
"""Force Panel to refresh all components"""
# Find all Panel panes and trigger refresh
for attr_name in dir(self):
attr = getattr(self, attr_name)
# Check if it's a Panel pane
if hasattr(attr, 'param') and hasattr(attr, 'object'):
try:
# Trigger refresh
attr.param.trigger('object')
self.logger.info(f"Triggered refresh for {attr_name}")
except Exception as e:
self.logger.debug(f"Could not trigger {attr_name}: {e}")
# Attach method to instance
dashboard_instance.force_panel_refresh = force_panel_refresh.__get__(dashboard_instance)
# Example usage in gen_dash.py:
"""
# In EnergyDashboard.__init__():
from fix_midnight_display_freeze import (
create_recreate_all_plots_method,
create_force_panel_refresh_method
)
# Add the new methods
create_recreate_all_plots_method(self)
create_force_panel_refresh_method(self)
# Then in auto_update_loop, after detecting date range change:
if old_range != new_range:
self.recreate_all_plots() # or self.force_panel_refresh()
"""