Aliazimi00 commited on
Commit
edcecf8
·
verified ·
1 Parent(s): d2f5268

Delete core/plot.py

Browse files
Files changed (1) hide show
  1. core/plot.py +0 -523
core/plot.py DELETED
@@ -1,523 +0,0 @@
1
- import matplotlib.pyplot as plt
2
- import seaborn as sns
3
- import pandas as pd
4
- import numpy as np
5
- import networkx as nx
6
- from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
7
-
8
- def plot_forecast(result):
9
- """Interactive backtest plot with zoom and pan functionality"""
10
- forecast = result["forecast"]
11
- actual = result["actual"]
12
-
13
- # Convert to numpy arrays and flatten if needed
14
- forecast = np.array(forecast).flatten()
15
- actual = np.array(actual).flatten()
16
-
17
- # Ensure both arrays have the same length
18
- min_len = min(len(forecast), len(actual))
19
- forecast = forecast[:min_len]
20
- actual = actual[:min_len]
21
-
22
- # Create figure with adjusted size for better visibility
23
- fig = plt.figure(figsize=(10, 6))
24
- ax = fig.add_subplot(111)
25
- plt.ion() # Turn on interactive mode
26
-
27
- if len(actual) == 0 or len(forecast) == 0:
28
- ax.text(0.5, 0.5, "No data available for plotting", ha='center', va='center')
29
- return fig
30
-
31
- # Create time indices
32
- time_indices = np.arange(len(actual))
33
-
34
- # Plot full historical actual
35
- ax.plot(time_indices, actual, label="Historical Actual", color="blue", linewidth=2, alpha=0.7)
36
-
37
- # Plot full historical forecast
38
- ax.plot(time_indices, forecast, label="Historical Forecast", color="orange", linestyle="--", linewidth=2, alpha=0.7)
39
-
40
- if len(actual) > 1 and len(forecast) > 1:
41
- last_idx = len(actual) - 1
42
-
43
- # Highlight only the last day actual segment
44
- last_actual_segment = [float(actual[last_idx-1]), float(actual[last_idx])]
45
- last_time_segment = [time_indices[last_idx-1], time_indices[last_idx]]
46
- ax.plot(last_time_segment, last_actual_segment,
47
- color="blue", linewidth=4, alpha=1.0)
48
-
49
- # Add markers for the last day comparison
50
- ax.scatter([last_idx], [float(actual[last_idx])], color="blue", s=150, zorder=5, edgecolors='darkblue', linewidth=2)
51
- ax.scatter([last_idx], [float(forecast[last_idx])], color="red", s=150, zorder=5, edgecolors='darkred', linewidth=2)
52
-
53
- # Add value annotations for last day
54
- actual_val = float(actual[last_idx])
55
- forecast_val = float(forecast[last_idx])
56
- annotation1 = ax.annotate(f'Actual: {actual_val:.2f}',
57
- xy=(last_idx, actual_val),
58
- xytext=(10, 10), textcoords='offset points',
59
- bbox=dict(boxstyle='round,pad=0.3', facecolor='blue', alpha=0.8, edgecolor='darkblue'),
60
- color='white', fontsize=10, fontweight='bold')
61
- annotation2 = ax.annotate(f'Predicted: {forecast_val:.2f}',
62
- xy=(last_idx, forecast_val),
63
- xytext=(10, -25), textcoords='offset points',
64
- bbox=dict(boxstyle='round,pad=0.3', facecolor='red', alpha=0.8, edgecolor='darkred'),
65
- color='white', fontsize=10, fontweight='bold')
66
- elif len(actual) == 1:
67
- # Handle single point case
68
- ax.scatter([0], [float(actual[0])], color="blue", s=150, zorder=5)
69
- ax.scatter([0], [float(forecast[0])], color="red", s=150, zorder=5)
70
-
71
- # Enhanced legend with interactive features, moved to top
72
- legend = ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=3, frameon=True, fancybox=True, shadow=True)
73
- legend.set_draggable(True) # Make legend draggable
74
-
75
- # Enhanced styling
76
- ax.set_xlabel("Time Index", fontsize=12)
77
- ax.set_ylabel("Value", fontsize=12)
78
- ax.grid(True, alpha=0.3, linestyle=':', linewidth=0.8)
79
-
80
- # Add hover functionality
81
- def on_hover(event):
82
- if event.inaxes == ax and len(actual) > 0:
83
- closest_idx = int(round(event.xdata)) if event.xdata is not None else 0
84
- if 0 <= closest_idx < len(actual):
85
- ax.set_xlabel(f"Time Index: {closest_idx}, Actual={float(actual[closest_idx]):.2f}, Forecast={float(forecast[closest_idx]):.2f}",
86
- fontsize=12)
87
- fig.canvas.draw_idle()
88
- else:
89
- ax.set_xlabel("Time Index", fontsize=12)
90
- fig.canvas.draw_idle()
91
-
92
- # Connect hover event
93
- fig.canvas.mpl_connect('motion_notify_event', on_hover)
94
-
95
- # Explicitly enable navigation toolbar
96
- toolbar = NavigationToolbar2Tk(fig.canvas, fig.canvas.get_tk_widget().master)
97
- toolbar.update()
98
- fig.canvas.toolbar_visible = True
99
-
100
- # Adjust layout to ensure toolbar visibility
101
- plt.tight_layout(rect=[0, 0, 1, 0.95])
102
- return fig
103
-
104
-
105
- def plot_future_forecast(df, result, future_df):
106
- """Interactive future forecast plot with zoom, pan and hover functionality"""
107
- # Create figure with adjusted size for better visibility
108
- fig = plt.figure(figsize=(10, 6))
109
- ax = fig.add_subplot(111)
110
- plt.ion() # Turn on interactive mode
111
-
112
- # Validate and convert data
113
- if df.empty or 'Date' not in df.columns or 'value' not in df.columns:
114
- ax.text(0.5, 0.5, "No valid historical data available", ha='center', va='center')
115
- return fig
116
-
117
- # Plot historical data
118
- dates = pd.to_datetime(df['Date'])
119
- values = np.array(df['value']).flatten()
120
- historical_line, = ax.plot(dates, values, label="Historical Data", color="blue", linewidth=2.5, alpha=0.9)
121
-
122
- # Store data for hover functionality
123
- plot_data = {
124
- 'historical_dates': dates,
125
- 'historical_values': values,
126
- 'future_dates': None,
127
- 'predictions': None,
128
- 'actual_future_dates': None,
129
- 'actual_future_values': None
130
- }
131
-
132
- if "latest_prediction" in result and len(result["latest_prediction"]) > 0:
133
- # Convert predictions to flat array
134
- predictions = np.array(result["latest_prediction"]).flatten()
135
-
136
- # Create future dates
137
- last_date = dates.iloc[-1] if len(dates) > 0 else pd.Timestamp.now()
138
- horizon = len(predictions)
139
-
140
- try:
141
- future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=horizon, freq='B')
142
- except:
143
- # Fallback to daily frequency if business day fails
144
- future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=horizon, freq='D')
145
-
146
- # Store for hover
147
- plot_data['future_dates'] = future_dates
148
- plot_data['predictions'] = predictions
149
-
150
- if len(values) > 0 and len(predictions) > 0:
151
- # Create connection from last historical point to first prediction
152
- connection_dates = [last_date, future_dates[0]]
153
- connection_values = [float(values[-1]), float(predictions[0])]
154
- connection_line, = ax.plot(connection_dates, connection_values, color="orange", linestyle=":", linewidth=2, alpha=0.7)
155
-
156
- # Plot forecast with fine connecting lines
157
- predictions_float = [float(p) for p in predictions]
158
- forecast_line, = ax.plot(future_dates, predictions_float,
159
- label="Forecast", color="orange", linewidth=3,
160
- linestyle="-", marker='o', markersize=8, markerfacecolor='orange',
161
- markeredgecolor='darkorange', markeredgewidth=2, alpha=0.9)
162
-
163
- # Add value annotations for predictions (initially hidden for cleaner zoom)
164
- prediction_annotations = []
165
- for i, val in enumerate(predictions_float):
166
- ann = ax.annotate(f"{val:.2f}",
167
- xy=(future_dates[i], val),
168
- xytext=(0, 15), textcoords='offset points',
169
- ha='center', va='bottom',
170
- bbox=dict(boxstyle='round,pad=0.3', facecolor='orange', alpha=0.9, edgecolor='darkorange'),
171
- color='white', fontsize=9, fontweight='bold',
172
- visible=False) # Initially hidden
173
- prediction_annotations.append(ann)
174
-
175
- # Plot actual future values if available
176
- if not future_df.empty and "future_actuals" in result and 'Date' in future_df.columns and 'value' in future_df.columns:
177
- actual_future_dates = pd.to_datetime(future_df['Date'])
178
- actual_future_values = np.array(future_df['value']).flatten()
179
- actual_future_values_float = [float(v) for v in actual_future_values]
180
-
181
- # Store for hover
182
- plot_data['actual_future_dates'] = actual_future_dates
183
- plot_data['actual_future_values'] = actual_future_values_float
184
-
185
- # Connect to actual future values
186
- actual_line, = ax.plot(actual_future_dates, actual_future_values_float,
187
- label="Actual Future", color="green", linewidth=3,
188
- linestyle="-", marker='s', markersize=8, markerfacecolor='green',
189
- markeredgecolor='darkgreen', markeredgewidth=2, alpha=0.9)
190
-
191
- # Add value annotations for actuals (initially hidden)
192
- actual_annotations = []
193
- for i, val in enumerate(actual_future_values_float):
194
- date_val = actual_future_dates.iloc[i] if hasattr(actual_future_dates, 'iloc') else actual_future_dates[i]
195
- ann = ax.annotate(f"{val:.2f}",
196
- xy=(date_val, val),
197
- xytext=(0, -25), textcoords='offset points',
198
- ha='center', va='top',
199
- bbox=dict(boxstyle='round,pad=0.3', facecolor='green', alpha=0.9, edgecolor='darkgreen'),
200
- color='white', fontsize=9, fontweight='bold',
201
- visible=False) # Initially hidden
202
- actual_annotations.append(ann)
203
-
204
- # Enhanced legend with interactive features, moved to top
205
- legend = ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=3, frameon=True, fancybox=True, shadow=True, fontsize=11)
206
- legend.set_draggable(True) # Make legend draggable
207
-
208
- ax.set_xlabel("Date", fontsize=13, fontweight='bold')
209
- ax.set_ylabel("Stock Price", fontsize=13, fontweight='bold')
210
- ax.grid(True, alpha=0.4, linestyle=':', linewidth=0.8)
211
-
212
- # Add crosshair cursor
213
- cursor_lines = {
214
- 'h': ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5, visible=False),
215
- 'v': ax.axvline(x=dates.iloc[0], color='gray', linestyle='--', alpha=0.5, visible=False)
216
- }
217
-
218
- # Enhanced hover functionality with crosshair
219
- def on_hover(event):
220
- if event.inaxes == ax and event.xdata is not None and event.ydata is not None:
221
- # Show crosshair
222
- cursor_lines['h'].set_ydata([event.ydata])
223
- cursor_lines['v'].set_xdata([event.xdata])
224
- cursor_lines['h'].set_visible(True)
225
- cursor_lines['v'].set_visible(True)
226
-
227
- # Find closest point and display info
228
- hover_date = pd.to_datetime(event.xdata)
229
- info_text = f"Date: {hover_date.strftime('%Y-%m-%d')}, Price: {event.ydata:.2f}"
230
-
231
- # Check if hovering over historical data
232
- if plot_data['historical_dates'] is not None:
233
- hist_diff = abs((plot_data['historical_dates'] - hover_date).days)
234
- if len(hist_diff) > 0 and hist_diff.min() < 5: # Within 5 days
235
- closest_idx = hist_diff.argmin()
236
- closest_val = float(plot_data['historical_values'][closest_idx])
237
- info_text = f"Historical - {plot_data['historical_dates'].iloc[closest_idx].strftime('%Y-%m-%d')}: ${closest_val:.2f}"
238
-
239
- # Check if hovering over predictions
240
- if plot_data['future_dates'] is not None:
241
- future_diff = abs((plot_data['future_dates'] - hover_date).days)
242
- if len(future_diff) > 0 and future_diff.min() < 2: # Within 2 days
243
- closest_idx = future_diff.argmin()
244
- closest_val = float(plot_data['predictions'][closest_idx])
245
- info_text = f"Prediction - {plot_data['future_dates'][closest_idx].strftime('%Y-%m-%d')}: ${closest_val:.2f}"
246
-
247
- # Check if hovering over actual future
248
- if plot_data['actual_future_dates'] is not None:
249
- actual_diff = abs((plot_data['actual_future_dates'] - hover_date).days)
250
- if len(actual_diff) > 0 and actual_diff.min() < 2:
251
- closest_idx = actual_diff.argmin()
252
- closest_val = float(plot_data['actual_future_values'][closest_idx])
253
- date_val = plot_data['actual_future_dates'].iloc[closest_idx] if hasattr(plot_data['actual_future_dates'], 'iloc') else plot_data['actual_future_dates'][closest_idx]
254
- info_text = f"Actual Future - {date_val.strftime('%Y-%m-%d')}: ${closest_val:.2f}"
255
-
256
- ax.set_xlabel(f"{info_text}", fontsize=13, fontweight='bold')
257
- fig.canvas.draw_idle()
258
-
259
- def on_leave(event):
260
- # Hide crosshair when leaving plot area
261
- cursor_lines['h'].set_visible(False)
262
- cursor_lines['v'].set_visible(False)
263
- ax.set_xlabel("Date", fontsize=13, fontweight='bold')
264
- fig.canvas.draw_idle()
265
-
266
- # Toggle annotations on double click
267
- def on_double_click(event):
268
- if event.inaxes == ax:
269
- # Toggle prediction annotations visibility
270
- if 'prediction_annotations' in locals():
271
- for ann in prediction_annotations:
272
- ann.set_visible(not ann.get_visible())
273
- if 'actual_annotations' in locals():
274
- for ann in actual_annotations:
275
- ann.set_visible(not ann.get_visible())
276
- fig.canvas.draw_idle()
277
-
278
- # Connect events
279
- fig.canvas.mpl_connect('motion_notify_event', on_hover)
280
- fig.canvas.mpl_connect('axes_leave_event', on_leave)
281
- fig.canvas.mpl_connect('button_press_event', on_double_click)
282
-
283
- # Improve date formatting on x-axis
284
- fig.autofmt_xdate(rotation=45)
285
-
286
- # Explicitly enable navigation toolbar
287
- toolbar = NavigationToolbar2Tk(fig.canvas, fig.canvas.get_tk_widget().master)
288
- toolbar.update()
289
- fig.canvas.toolbar_visible = True
290
-
291
- # Adjust layout to ensure toolbar visibility
292
- plt.tight_layout(rect=[0, 0, 1, 0.95])
293
- return fig
294
-
295
-
296
- def plot_metrics_precision(result):
297
- fig = plt.figure(figsize=(8, 5))
298
- ax = fig.add_subplot(111)
299
- metrics = {k: v for k, v in result['metrics'].items() if k in ['R² (%)', 'Explained Variance (%)', 'MDA (%)'] and v is not None}
300
- if not metrics:
301
- ax.text(0.5, 0.5, "No valid precision metrics available", ha='center', va='center')
302
- return fig
303
-
304
- sns.barplot(x=list(metrics.keys()), y=list(metrics.values()), ax=ax, palette="Blues_d")
305
- for i, v in enumerate(metrics.values()):
306
- ax.text(i, v + 0.01 * max(metrics.values(), default=100), f"{v:.2f}%", ha='center', va='bottom', fontsize=10)
307
-
308
- max_val = max(metrics.values(), default=100)
309
- min_val = min(metrics.values(), default=0)
310
- ax.set_ylim(min(min_val - 5, -10), max_val + 10)
311
- ax.set_ylabel("Value (%)")
312
- ax.grid(True, axis='y')
313
-
314
- # Explicitly enable navigation toolbar
315
- toolbar = NavigationToolbar2Tk(fig.canvas, fig.canvas.get_tk_widget().master)
316
- toolbar.update()
317
- fig.canvas.toolbar_visible = True
318
-
319
- plt.tight_layout()
320
- return fig
321
-
322
-
323
- def plot_metrics_risk(result):
324
- fig = plt.figure(figsize=(8, 5))
325
- ax = fig.add_subplot(111)
326
- metrics = {k: v for k, v in result['metrics'].items() if k in ['RMSE', 'MAE', 'MAPE (%)', 'MASE'] and v is not None}
327
- if not metrics:
328
- ax.text(0.5, 0.5, "No valid risk metrics available", ha='center', va='center')
329
- return fig
330
-
331
- sns.barplot(x=list(metrics.keys()), y=list(metrics.values()), ax=ax, palette="Reds_d")
332
- for i, v in enumerate(metrics.values()):
333
- ax.text(i, v + 0.01 * max(metrics.values(), default=1), f"{v:.2f}", ha='center', va='bottom', fontsize=10)
334
-
335
- max_val = max(metrics.values(), default=1)
336
- ax.set_ylim(0, max_val + 0.2 * max_val)
337
- ax.set_ylabel("Value")
338
- ax.grid(True, axis='y')
339
-
340
- # Explicitly enable navigation toolbar
341
- toolbar = NavigationToolbar2Tk(fig.canvas, fig.canvas.get_tk_widget().master)
342
- toolbar.update()
343
- fig.canvas.toolbar_visible = True
344
-
345
- plt.tight_layout()
346
- return fig
347
-
348
-
349
- def plot_loss_curve(result):
350
- train_losses = result.get('train_loss', [])
351
- val_losses = result.get('val_loss', [])
352
- fig = plt.figure(figsize=(8, 5))
353
- ax = fig.add_subplot(111)
354
- ax.plot(train_losses, label='Train Loss', color='blue', linewidth=2)
355
- if val_losses:
356
- ax.plot(val_losses, label='Validation Loss', color='orange', linewidth=2)
357
- ax.set_xlabel('Epoch')
358
- ax.set_ylabel('Loss (MSE)')
359
- ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=2)
360
- ax.grid(True)
361
-
362
- # Explicitly enable navigation toolbar
363
- toolbar = NavigationToolbar2Tk(fig.canvas, fig.canvas.get_tk_widget().master)
364
- toolbar.update()
365
- fig.canvas.toolbar_visible = True
366
-
367
- plt.tight_layout()
368
- return fig
369
-
370
-
371
- def plot_model_architecture(result):
372
- fig = plt.figure(figsize=(10, 6))
373
- ax = fig.add_subplot(111)
374
- ax.axis('off')
375
- G = nx.DiGraph()
376
-
377
- if "architecture" not in result:
378
- ax.text(0.5, 0.5, "No architecture details available", ha='center', va='center', fontsize=12)
379
- return fig
380
-
381
- arch = result["architecture"]
382
- model_name = arch["model_name"]
383
- num_layers = arch["num_layers"]
384
- hidden_units = arch["hidden_units"]
385
- dropout = arch["dropout"]
386
- batch_size = arch["batch_size"]
387
- input_size = arch["input_size"]
388
- output_size = arch["output_size"]
389
-
390
- # Handle model-specific hidden units for visualization
391
- if model_name == "MLPModel":
392
- hidden_nodes = min(hidden_units[0], 5) # Use first hidden layer size
393
- units_label = f"{hidden_units[0]},{hidden_units[1]}" # Show both sizes
394
- elif model_name == "CNNModel":
395
- hidden_nodes = 5 # Simplified for visualization
396
- units_label = f"{hidden_units} filters" # num_filters
397
- elif model_name == "TransformerModel":
398
- hidden_nodes = min(hidden_units, 5) # d_model
399
- units_label = f"{hidden_units}"
400
- else:
401
- hidden_nodes = min(hidden_units, 5) # hidden_size
402
- units_label = f"{hidden_units}"
403
-
404
- # Simplified block diagram for complex models
405
- if model_name in ["CNNModel", "HybridModel", "CNN_GRU"]:
406
- G = nx.DiGraph()
407
- pos = {}
408
- nodes = []
409
- y_pos = 0.5
410
- layer_width = 1.0 / 4 # Fixed for 3–4 blocks
411
-
412
- # Define components for each complex model
413
- if model_name == "CNNModel":
414
- components = [
415
- ("Input", f"{input_size} units"),
416
- ("Conv1D", f"{hidden_units} filters"),
417
- ("MaxPool", ""),
418
- ("Output", f"{output_size} units")
419
- ]
420
- elif model_name == "HybridModel":
421
- components = [
422
- ("Input", f"{input_size} units"),
423
- ("Conv1D", "32 filters"),
424
- (f"BiLSTM ({num_layers} layers)", f"{hidden_units*2} units"),
425
- ("Output", f"{output_size} units")
426
- ]
427
- elif model_name == "CNN_GRU":
428
- components = [
429
- ("Input", f"{input_size} units"),
430
- ("Conv1D", "32 filters"),
431
- (f"GRU ({num_layers} layers)", f"{hidden_units} units"),
432
- ("Output", f"{output_size} units")
433
- ]
434
-
435
- # Add nodes and positions
436
- for i, (comp, label) in enumerate(components):
437
- G.add_node(comp, layer=comp)
438
- pos[comp] = (i * layer_width, y_pos)
439
- nodes.append([comp])
440
- if i > 0:
441
- G.add_edge(components[i-1][0], comp)
442
-
443
- # Draw the graph
444
- nx.draw(G, pos, ax=ax, with_labels=False, node_color='lightblue', edge_color='gray',
445
- node_size=2000, node_shape='s', arrowsize=10)
446
-
447
- # Add labels for each block
448
- for node, (x, y) in pos.items():
449
- label = [comp[1] for comp in components if comp[0] == node][0]
450
- ax.text(x, y + 0.05, f"{node}\n{label}", ha='center', va='bottom', fontsize=8,
451
- bbox=dict(facecolor='white', alpha=0.8, edgecolor='black'))
452
-
453
- else:
454
- # Node-based graph for RNN, LSTM, GRU, BiLSTM, MLP, Transformer
455
- max_nodes_display = 5
456
- input_nodes = min(input_size, max_nodes_display)
457
- output_nodes = min(output_size, max_nodes_display)
458
-
459
- nodes = []
460
- pos = {}
461
- layer_width = 1.0 / (num_layers + 2)
462
- y_pos = 0.5
463
-
464
- # Input layer
465
- for i in range(input_nodes):
466
- node = f"input_{i}"
467
- G.add_node(node, layer="input")
468
- pos[node] = (0, y_pos + (i - input_nodes / 2) * 0.1)
469
- nodes.append([f"input_{i}" for i in range(input_nodes)])
470
-
471
- # Hidden layers
472
- for layer in range(num_layers):
473
- layer_nodes = []
474
- for i in range(hidden_nodes):
475
- node = f"hidden_{layer}_{i}"
476
- G.add_node(node, layer=f"hidden_{layer+1}")
477
- pos[node] = ((layer + 1) * layer_width, y_pos + (i - hidden_nodes / 2) * 0.1)
478
- layer_nodes.append(node)
479
- nodes.append(layer_nodes)
480
-
481
- # Output layer
482
- output_layer_nodes = []
483
- for i in range(output_nodes):
484
- node = f"output_{i}"
485
- G.add_node(node, layer="output")
486
- pos[node] = ((num_layers + 1) * layer_width, y_pos + (i - output_nodes / 2) * 0.1)
487
- output_layer_nodes.append(node)
488
- nodes.append(output_layer_nodes)
489
-
490
- # Add edges between layers
491
- for layer in range(len(nodes) - 1):
492
- for src in nodes[layer]:
493
- for dst in nodes[layer + 1]:
494
- G.add_edge(src, dst)
495
-
496
- # Draw the graph
497
- nx.draw(G, pos, ax=ax, with_labels=False, node_color='lightblue', edge_color='gray',
498
- node_size=500, arrowsize=10)
499
-
500
- # Add layer labels
501
- for node in G.nodes(data=True):
502
- layer = node[1]['layer']
503
- x, y = pos[node[0]]
504
- if layer.startswith("hidden"):
505
- label = f"Layer {layer.split('_')[1]}: {units_label} units"
506
- elif layer == "input":
507
- label = f"Input: {input_size} units"
508
- elif layer == "output":
509
- label = f"Output: {output_size} units"
510
- ax.text(x, y + 0.05, label, ha='center', va='bottom', fontsize=8)
511
-
512
- # Add model details as annotation
513
- details = f"Dropout: {dropout:.2f}\nBatch Size: {batch_size}"
514
- ax.text(0.5, 0.05, details, ha='center', va='bottom', fontsize=10, transform=ax.transAxes,
515
- bbox=dict(facecolor='white', alpha=0.8, edgecolor='black'))
516
-
517
- # Explicitly enable navigation toolbar
518
- toolbar = NavigationToolbar2Tk(fig.canvas, fig.canvas.get_tk_widget().master)
519
- toolbar.update()
520
- fig.canvas.toolbar_visible = True
521
-
522
- plt.tight_layout()
523
- return fig