fix(psd): auto-scale x-axis to data range, improve plot layout

- X-axis now auto-ranges from data (was going to 10^21)
- Band annotations clamped to actual data extent
- Legend moved to upper-right (was overlapping data)
- Thicker lines (2.5px), larger axis labels
- dtick=1 for clean log-scale tick marks
This commit is contained in:
2026-01-30 00:03:38 +00:00
parent 12afd0c54f
commit 27d9dbee5b

View File

@@ -381,36 +381,55 @@ def make_psd_plot(psd_data_dict, title="Power Spectral Density"):
'90° (Abs)': '#16a34a', '20° (Abs)': '#64748b'}
fig = go.Figure()
# Determine axis range from data
all_freqs = []
all_psd = []
for label, (freqs, psd) in psd_data_dict.items():
valid = psd > 0
f_valid, p_valid = freqs[valid], psd[valid]
all_freqs.extend(f_valid.tolist())
all_psd.extend(p_valid.tolist())
fig.add_trace(go.Scatter(
x=freqs[valid], y=psd[valid],
x=f_valid, y=p_valid,
mode='lines', name=label,
line=dict(color=colors.get(label, '#6366f1'), width=2),
line=dict(color=colors.get(label, '#6366f1'), width=2.5),
hovertemplate="%{x:.1f} cyc/apt: %{y:.2e}<extra>" + label + "</extra>",
))
# Band annotations
fig.add_vrect(x0=0.1, x1=2.0, fillcolor='rgba(59,130,246,0.08)', line_width=0, layer='below',
# Compute axis limits from actual data
if all_freqs:
f_min = max(0.05, min(all_freqs) * 0.5)
f_max = max(all_freqs) * 2.0
else:
f_min, f_max = 0.05, 200.0
# Band annotations — clamped to data range
band_max = min(f_max, 200.0)
fig.add_vrect(x0=0.1, x1=2.0, fillcolor='rgba(59,130,246,0.07)', line_width=0, layer='below',
annotation_text="Gravity", annotation_position="top left",
annotation=dict(font=dict(size=10, color='#3b82f6')))
fig.add_vrect(x0=2.0, x1=20.0, fillcolor='rgba(245,158,11,0.08)', line_width=0, layer='below',
annotation=dict(font=dict(size=11, color='#3b82f6')))
fig.add_vrect(x0=2.0, x1=20.0, fillcolor='rgba(245,158,11,0.07)', line_width=0, layer='below',
annotation_text="Support", annotation_position="top left",
annotation=dict(font=dict(size=10, color='#f59e0b')))
fig.add_vrect(x0=20.0, x1=200.0, fillcolor='rgba(239,68,68,0.06)', line_width=0, layer='below',
annotation_text="High Freq", annotation_position="top left",
annotation=dict(font=dict(size=10, color='#ef4444')))
annotation=dict(font=dict(size=11, color='#f59e0b')))
if band_max > 20:
fig.add_vrect(x0=20.0, x1=band_max, fillcolor='rgba(239,68,68,0.05)', line_width=0, layer='below',
annotation_text="High Freq", annotation_position="top left",
annotation=dict(font=dict(size=11, color='#ef4444')))
fig.update_layout(
height=500, width=1100,
margin=dict(t=30, b=60, l=80, r=30),
**_PLOTLY_LIGHT_LAYOUT,
xaxis=dict(type='log', title=dict(text="Spatial Frequency [cycles/aperture]", font=dict(color='#1e293b')),
gridcolor='#e2e8f0', tickfont=dict(color='#475569')),
yaxis=dict(type='log', title=dict(text="PSD [nm²·mm²]", font=dict(color='#1e293b')),
gridcolor='#e2e8f0', tickfont=dict(color='#475569')),
legend=dict(x=0.01, y=0.99, bgcolor='rgba(255,255,255,0.9)',
bordercolor='#e2e8f0', borderwidth=1, font=dict(size=11, color='#1e293b')),
xaxis=dict(type='log',
title=dict(text="Spatial Frequency [cycles/aperture]", font=dict(color='#1e293b', size=13)),
range=[np.log10(f_min), np.log10(f_max)],
gridcolor='#e2e8f0', tickfont=dict(color='#475569', size=11),
dtick=1),
yaxis=dict(type='log',
title=dict(text="PSD [nm\u00b2\u00b7mm\u00b2]", font=dict(color='#1e293b', size=13)),
gridcolor='#e2e8f0', tickfont=dict(color='#475569', size=11)),
legend=dict(x=0.70, y=0.98, bgcolor='rgba(255,255,255,0.92)',
bordercolor='#e2e8f0', borderwidth=1, font=dict(size=12, color='#1e293b')),
)
return fig.to_html(include_plotlyjs=False, full_html=False, div_id="psd_plot")