Plotting & visualisation

Every fit, posterior, and spectrum produced by the suite can be visualised with a small, consistent set of plotting helpers. They fall into three families:

  • Static (matplotlib) — publication-quality figures you save to PDF/PNG.

  • Interactive (plotly) — zoom/pan/hover figures for exploration, with an optional stacked 2-D rectified-spectrum panel.

  • MCMC diagnostics — corner plots, trace plots, and flux posteriors for jwspecmcmc results.

In addition, the redshift and DLA result objects carry their own .plot() convenience methods.

At a glance

Function

Package

Backend

Input

Use for

plot_fit()

jwspecfit

matplotlib

FitResult

Publication figure of a completed fit (data + model + components + residuals)

plot_fit_interactive()

jwspecfit

plotly

FitResult

Interactive inspection of a fit; optional 2-D panel

plot_spectrum_interactive()

jwspecfit

plotly

Spectrum / path(s)

Look at one or more spectra before fitting; overplot lines

plot_2d_1d()

jwspecfit

matplotlib

.spec.fits path

Static stacked 2-D + 1-D figure straight from a file

plot_corner()

jwspecmcmc

matplotlib

MCMCResult

Posterior covariances / marginals

plot_traces()

jwspecmcmc

matplotlib

MCMCResult

Chain mixing / convergence (emcee chains)

plot_flux_posterior()

jwspecmcmc

matplotlib

MCMCResult

Flux posterior for one line

Tip

An MCMCResult is accepted directly by plot_fit() and plot_fit_interactive() — they auto-convert via to_fit_result(). You do not need to convert manually to draw the fit.


Static fit figures — plot_fit

The workhorse for papers. Given a FitResult (from fit_lines(), fit_with_broad(), or an MCMCResult), it draws the data, the total model, the continuum, each Gaussian component, and a residual panel — with the y-axis scaled to the tallest emission line rather than to noise spikes.

import jwspecfit

result = jwspecfit.fit_lines("spectrum.fits", z=6.0)

# Quick look
fig = jwspecfit.plot_fit(result)

# Publication figure, saved straight to PDF
fig = jwspecfit.plot_fit(
    result,
    flux_unit="flam",          # erg/s/cm²/Å instead of µJy
    rest_frame=True,           # divide wavelengths by (1+z)
    show_components=True,      # filled Gaussians; broad lines hatched
    exclude_wave_A=[(13000, 13500)],  # mask a noisy detector gap
    save_path="fit.pdf",
)

Key parameters:

flux_unit

"fnu" (µJy, default) or "flam" (erg/s/cm²/Å).

wave_unit

"A" (Ångström, default) or "um" (microns).

rest_frame

When True, divides wavelengths by (1 + z) using the spectrum’s stored redshift. Axis labels switch to “Rest Wavelength”.

show_components

Draw each Gaussian as a filled curve. Broad Balmer components are drawn with hatching so they read clearly against the narrow lines.

show_residuals

Toggle the lower residual panel (default True).

y_pad

Multiplicative headroom above the tallest line peak (default 1.3).

exclude_wave_A

List of (lo, hi) ranges in Ångström to hide — useful for masking detector gaps or contaminated regions.

save_path

Write the figure to disk (.pdf, .png, …). Returns the figure either way.

See also

Full parameter list: jwspecfit.plot_fit().


Static 2-D + 1-D — plot_2d_1d

Renders a stacked figure — the 2-D rectified spectrum on top, the 1-D extraction below — directly from an msaexp-style .spec.fits file, with no fit required. Ideal for a first look at the data or a context panel in a figure.

fig, (ax_2d, ax_1d) = jwspecfit.plot_2d_1d(
    "target.spec.fits",
    z=6.0,                     # places observed-frame line markers
    cmap="Blues",
    flux_scale=1000.0,         # µJy → nJy on the 1-D panel
    flux_label=r"$F_\nu$ [nJy]",
    add_lines={"Mg II 2796": 2796.352},  # extra label not in the defaults
)
fig.savefig("context.pdf", dpi=300)

It returns the figure and both axes, so you can annotate or restyle afterwards (e.g. ax_1d.set_xlim(...)). The lines / add_lines arguments take the same forms as the interactive helpers — see Marking emission lines.

See also

Full parameter list: jwspecfit.plot_2d_1d().


Interactive spectra — plot_spectrum_interactive

Open one or more 1-D spectra in plotly before fitting — zoom, pan, hover read-outs, an optional 2-D panel, and overplotted line markers. Accepts a Spectrum, a file path, or a list of either (to overplot several spectra).

# Single file
fig = jwspecfit.plot_spectrum_interactive("spectrum.fits", z=6.0)

# A rest-frame stack saved as .npz
fig = jwspecfit.plot_spectrum_interactive("stack.npz", rest_frame=True)

# An in-memory Spectrum, in F_lambda units
fig = jwspecfit.plot_spectrum_interactive(spec, flux_unit="flam")

# Overplot several spectra with custom legend labels
fig = jwspecfit.plot_spectrum_interactive(
    ["g140m.fits", "g235m.fits"],
    z=6.0,
    labels=["G140M", "G235M"],
)
fig.show()

The 2-D rectified panel appears automatically (show_2d="auto") whenever a single spectrum is supplied and its sci_2d array is populated (e.g. read from a .spec.fits file). Set show_2d=False to force 1-D only.

Any extra keyword arguments are forwarded to the file reader, so FITS column overrides work inline:

fig = jwspecfit.plot_spectrum_interactive(
    "custom.fits", z=6.0, hdu="EXTRACT1D", wave_col="WAVELENGTH",
)

See also

Full parameter list: jwspecfit.plot_spectrum_interactive().


Interactive fits — plot_fit_interactive

The plotly counterpart to plot_fit. Same data/model/components/residual layout, but zoomable and with an optional stacked 2-D panel above the fit.

fig = jwspecfit.plot_fit_interactive(result)
fig.show()

# Rest-frame, with a curated set of line markers added on top
fig = jwspecfit.plot_fit_interactive(
    result, rest_frame=True, lines=None,  # None opts in to default markers
)

By default (lines=False) no curated markers are drawn, because every fitted line is already labelled at its peak above the model. Pass lines=None for the package-default marker set, or an explicit list of line keys to mark only those.

See also

Full parameter list: jwspecfit.plot_fit_interactive().


Marking emission lines

The interactive helpers and plot_2d_1d share a common vocabulary for emission-line markers via the lines and add_lines arguments.

lines

Controls the curated default set. None draws the package defaults, False draws none, and a list of keys from jwspecfit.lines.REST_LINES_A marks only those.

add_lines

Extra markers layered on top, in two accepted forms:

  • list of names from REST_LINES_A — wavelengths looked up automatically, e.g. add_lines=["H8", "HEPSILON", "FeII_2382"].

  • dict of {label: rest_wavelength_A} for free-form labels at an explicit rest wavelength in Ångström, e.g. add_lines={"Mg II 2796": 2796.352}.

# What line names are available?
jwspecfit.show_lines()

Markers are redshifted by (1 + z) (the effective z comes from the z argument, else the spectrum’s own spec.z); in rest-frame mode they sit at the rest wavelengths.


MCMC diagnostics

These live in jwspecmcmc and take an MCMCResult. See the jwspecmcmc guide for how to produce one.

Corner plots — plot_corner

Posterior marginals and pairwise covariances.

import jwspecmcmc

result = jwspecmcmc.fit_lines("spectrum.fits", z=6.0)

# All free parameters
fig = jwspecmcmc.plot_corner(result)

# A focused subset
fig = jwspecmcmc.plot_corner(
    result,
    params=["A_OIII_5007", "A_HBETA", "sigma_Ha"],
    quantiles=[0.16, 0.5, 0.84],   # default: 16/50/84th percentiles
)

Extra keyword arguments are forwarded to corner.corner.

Trace plots — plot_traces

Per-walker chains, for eyeballing mixing and convergence.

fig = jwspecmcmc.plot_traces(result)
fig = jwspecmcmc.plot_traces(result, params=["A_HBETA"])

Note

plot_traces needs per-chain samples, so it works for emcee results. A nautilus/NUTS result without stored chains raises ValueError — judge those runs with R̂ and ESS from result.summary() and with plot_corner.

Flux posteriors — plot_flux_posterior

Histogram of the integrated-flux posterior for a single line — the honest picture of a flux measurement and its (often asymmetric) uncertainty.

ax = jwspecmcmc.plot_flux_posterior(result, "OIII_5007", bins=50)

# Compose several onto shared axes
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
for line in ("OIII_5007", "HBETA"):
    jwspecmcmc.plot_flux_posterior(result, line, ax=ax)

Result-object convenience plots

Redshift scan — RedshiftResult.plot()

fit_redshift() returns a RedshiftResult whose .plot() draws a two-panel plotly diagnostic: Δχ²(z) with 1/2/3σ thresholds and candidate peaks on top, and the spectrum at the best redshift below.

zres = jwspecfit.fit_redshift("spectrum.fits")
zres.plot().show()

DLA fit — DLAResult.plot()

fit_NHI() returns a DLAResult whose .plot() overlays the fitted Lyα/DLA profile on the data with a residual panel.

result = jwspecfit.fit_NHI(spec, z=6.5, R=spec.R, n_live=400, seed=42)
print(result.summary())
result.plot(flux_unit="flam")

Choosing a backend

Static (matplotlib)

Interactive (plotly)

Best for

Paper figures, batch scripts

Exploration, picking masks, QA

Output

PDF / PNG via save_path / savefig

fig.show() in a notebook/browser

2-D panel

plot_2d_1d

show_2d="auto" in both interactive helpers

Returns

Figure (and axes for plot_2d_1d)

plotly.graph_objects.Figure

A common workflow: explore with plot_spectrum_interactive to choose exclude_wave_A masks and confirm the redshift, fit, then render the final figure with plot_fit(..., save_path=...).