diff --git a/fdm/visualizer.py b/fdm/visualizer.py index 6f245fa..752975b 100644 --- a/fdm/visualizer.py +++ b/fdm/visualizer.py @@ -27,52 +27,122 @@ def visualize_fdm(T_matrix, x_vals, t_vals): os.makedirs(out_dir, exist_ok=True) # ------------------------------------------------------------------ - # 1. Heatmap + # 1. heatmap.html: plot statico T(x,t) + striscia 1D animata + # Due figure Plotly indipendenti nello stesso file HTML per + # evitare conflitti tra animazioni e assi multipli. # ------------------------------------------------------------------ - zmax = float(np.max(np.abs(T_matrix - config.T0))) - # Use symmetric range centred on T0 if there is any variation, - # otherwise fall back to the raw range. - z_data = T_matrix.T # shape (NT, NX) — rows=time, cols=space + zmin_val = float(np.min(T_matrix)) + zmax_val = float(np.max(T_matrix)) - if zmax > 0: - z_center = float(np.mean(T_matrix)) - z_abs = float(np.max(np.abs(T_matrix - z_center))) - zmin_sym = z_center - z_abs - zmax_sym = z_center + z_abs - else: - zmin_sym = float(np.min(T_matrix)) - zmax_sym = float(np.max(T_matrix)) + # Subsample frames (shared with animation below) + n_frames = len(t_vals) + max_frames = 200 + step = max(1, n_frames // max_frames) + frame_indices = list(range(0, n_frames, step)) - fig_heatmap = go.Figure(go.Heatmap( + # --- Figura A: heatmap 2D statica (identica all'originale) --- + z_data = T_matrix.T # (NT, NX) + z_center = float(np.mean(T_matrix)) + z_abs = float(np.max(np.abs(T_matrix - z_center))) or 1.0 + fig_static = go.Figure(go.Heatmap( z=z_data, x=x_vals, y=t_vals, colorscale='RdBu_r', - zmin=zmin_sym, - zmax=zmax_sym, + zmin=z_center - z_abs, + zmax=z_center + z_abs, colorbar=dict(title='T [°C]'), )) - fig_heatmap.update_layout( + fig_static.update_layout( title='FDM — Heat Equation T(x,t)', xaxis_title='x [m]', yaxis_title='t [s]', - height=500, + height=480, + margin=dict(t=50, b=50, l=70, r=90), ) + # --- Figura B: striscia 1D animata --- + strip_frames = [ + go.Frame( + data=[go.Heatmap( + z=T_matrix[:, idx].reshape(1, -1), + x=x_vals, + y=[0], + colorscale='Jet', + zmin=zmin_val, + zmax=zmax_val, + showscale=True, + colorbar=dict(title='T [°C]', thickness=18), + )], + name=str(idx), + layout=go.Layout(title_text=f't = {t_vals[idx]:.3f} s'), + ) + for idx in frame_indices + ] + + fig_strip = go.Figure( + data=[go.Heatmap( + z=T_matrix[:, frame_indices[0]].reshape(1, -1), + x=x_vals, + y=[0], + colorscale='Jet', + zmin=zmin_val, + zmax=zmax_val, + showscale=True, + colorbar=dict(title='T [°C]', thickness=18), + )], + frames=strip_frames, + layout=go.Layout( + title=f't = {t_vals[frame_indices[0]]:.3f} s', + xaxis=dict(title='x [m]'), + yaxis=dict(showticklabels=False, showgrid=False, + zeroline=False, fixedrange=True), + height=300, + margin=dict(t=80, b=130, l=70, r=110), + updatemenus=[dict( + type='buttons', + showactive=False, + y=1.22, x=0.5, xanchor='center', + buttons=[ + dict(label='▶ Play', method='animate', + args=[None, dict(frame=dict(duration=40, redraw=True), + fromcurrent=True, mode='immediate')]), + dict(label='⏸ Pause', method='animate', + args=[[None], dict(frame=dict(duration=0, redraw=False), + mode='immediate')]), + ], + )], + sliders=[dict( + steps=[ + dict(method='animate', + args=[[str(idx)], dict(mode='immediate', + frame=dict(duration=0, redraw=True))], + label=f'{t_vals[idx]:.2f}') + for idx in frame_indices + ], + transition=dict(duration=0), + x=0.05, y=0, len=0.9, + currentvalue=dict(prefix='t = ', font=dict(size=14)), + )], + ), + ) + + # Scrivi entrambe le figure in un unico file HTML + html_static = fig_static.to_html(full_html=False, include_plotlyjs='cdn') + html_strip = fig_strip.to_html(full_html=False, include_plotlyjs=False) heatmap_path = os.path.join(out_dir, 'heatmap.html') - fig_heatmap.write_html(heatmap_path) + with open(heatmap_path, 'w', encoding='utf-8') as _f: + _f.write('
\n') + _f.write(html_static) + _f.write('\n') + _f.write(html_strip) + _f.write('\n') print(f"Heatmap saved → {heatmap_path}") # ------------------------------------------------------------------ # 2. Animated profile T(x) evolving in time # ------------------------------------------------------------------ L = config.L - n_frames = len(t_vals) - - # Subsample frames for a manageable animation (max ~200 frames) - max_frames = 200 - step = max(1, n_frames // max_frames) - frame_indices = list(range(0, n_frames, step)) y_min = float(np.min(T_matrix)) - 1.0 y_max = float(np.max(T_matrix)) + 1.0