diff --git a/plot_animazione_fem.py b/plot_animazione_fem.py index 678e021..27af358 100644 --- a/plot_animazione_fem.py +++ b/plot_animazione_fem.py @@ -102,10 +102,14 @@ def main() -> None: norm = matplotlib.colors.Normalize(vmin=T_ambiente, vmax=T_max) cmap = matplotlib.colormaps["inferno"] - fig = plt.figure(figsize=(9, 7)) - ax = fig.add_subplot(projection="3d") + fig = plt.figure(figsize=(9, 8.5)) + griglia = fig.add_gridspec(2, 1, height_ratios=[2.6, 1.0], hspace=0.05) + ax = fig.add_subplot(griglia[0], projection="3d") + ax_storia = fig.add_subplot(griglia[1]) ax.view_init(elev=35.264, azim=45) - ax.set_box_aspect((lunghezza_m, 2 * raggio_m, 2 * raggio_m)) + # zoom > 1 riempie il riquadro: con set_axis_off gli assi 3D lascerebbero + # altrimenti molto margine vuoto sopra il pannello della storia. + ax.set_box_aspect((lunghezza_m, 2 * raggio_m, 2 * raggio_m), zoom=1.12) ax.set_axis_off() ax.set_xlim(0.0, lunghezza_m) ax.set_ylim(-raggio_m, raggio_m) @@ -120,11 +124,38 @@ def main() -> None: mappabile = cm.ScalarMappable(cmap=cmap, norm=norm) mappabile.set_array([]) - barra = fig.colorbar(mappabile, ax=ax, shrink=0.6, pad=0.05) + # La colorbar è agganciata a entrambi i pannelli per non restringere solo + # quello 3D: così i due riquadri restano della stessa larghezza. + barra = fig.colorbar(mappabile, ax=(ax, ax_storia), shrink=0.6, pad=0.02, aspect=30) barra.set_label("T [°C]") titolo = ax.set_title("") + # Pannello inferiore: storia della temperatura nel punto osservato dal + # sensore. T_vere è il valore nodale calcolato dal FEM, T_lette lo stesso + # segnale filtrato dall'inerzia del primo ordine del sensore. + # Con la costante di tempo del sensore le due curve sono quasi + # sovrapposte: la prima è tracciata spessa e trasparente perché la seconda, + # tratteggiata, resti leggibile sopra di essa. + linea_vera, = ax_storia.plot( + [], [], color="tab:blue", linewidth=3.0, alpha=0.4, + label="T nodo del sensore (FEM)", + ) + linea_letta, = ax_storia.plot( + [], [], color="tab:red", linewidth=1.2, linestyle="--", + label="T misurata dal sensore (con inerzia)", + ) + cursore = ax_storia.axvline(tempi[indice_inizio], color="gray", linewidth=0.8) + ax_storia.set_xlim(0.0, tempi[-1]) + ax_storia.set_ylim( + T_ambiente - 0.05 * (T_max - T_ambiente), + max(dati["T_vere"].max(), dati["T_lette"].max()) * 1.08, + ) + ax_storia.set_xlabel("Tempo [s]") + ax_storia.set_ylabel("T [°C]") + ax_storia.legend(loc="upper left") + ax_storia.grid(True, alpha=0.3) + def disegna_frame(k: int): T_elementi = dati["campi"][k][elementi].mean(axis=1) colori = cmap(norm(T_elementi)) @@ -142,11 +173,15 @@ def main() -> None: np.full(n_visibili, raggio_m * np.cos(theta_sorgente) * 1.06), ) + linea_vera.set_data(tempi[: k + 1], dati["T_vere"][: k + 1]) + linea_letta.set_data(tempi[: k + 1], dati["T_lette"][: k + 1]) + cursore.set_xdata([tempi[k], tempi[k]]) + titolo.set_text( f"FEM shell — t = {tempi[k]:.2f} s — " f"T sensore = {dati['T_lette'][k]:.1f} °C" ) - return collezione, marker_sorgenti, titolo + return collezione, marker_sorgenti, linea_vera, linea_letta, cursore, titolo animazione = FuncAnimation( fig, disegna_frame, frames=indici_frame,