# plasma_plots

*module*

Plots and diagnostics of labeled xarray data from plasma simulations, such as Struphy's output.

plasma-plots adds accessors to the objects Struphy returns; you rarely call a function directly:

* ``out.plot`` and ``out.analysis`` on a Struphy ``Output``: whole-run plots and diagnostics.
* ``array.plasma.plot``, ``.analysis`` and ``.data`` on every ``xarray.DataArray``, e.g. a product
  from ``out.evaluate("em_fields/phi")``.
* ``dataset.plasma.plot``, ``.analysis`` and ``.data`` on marker Datasets such as orbits.

Creating a Struphy ``Output`` loads plasma-plots, so its output needs no import. For xarray data
from elsewhere, ``import plasma_plots`` first; without it, ``.plasma`` raises
``AttributeError: 'DataArray' object has no attribute 'plasma'``.

Printing an accessor lists its methods (``print(phi.plasma.plot)``), and ``help()`` on a method
shows every parameter (``help(phi.plasma.plot.slice)``). ``plasma-plots guide`` (or
``python -m plasma_plots guide``) prints this guide, ``plasma-plots api`` an index of every method and
function with its signature. The ``plasma-plots`` command also saves figures from the shell, e.g.
``plasma-plots plot sim_1 em_fields/phi slice t=-1 eta3=0 -o phi.png`` or
``plasma-plots quicklook sim_1 -o figures/`` (``plasma-plots --help``).

> **A post-processing pipeline**
>
> >>> from struphy import Output  # doctest: +SKIP
> >>> out = Output("sim_1")  # the run's output folder
> >>> out.plot.energies().save("energies.png")  # the energy budget and its drift
> >>> # dims (t, eta1, eta2, eta3), coordinates X, Y, Z
> >>> phi = out.evaluate("em_fields/phi")
> >>> phi.plasma.plot.slice(coords="physical", plane="XY", t=-1, eta3=0).save(
> ...     "phi.png"
> ... )
> >>> phi.plasma.plot.animation(x="eta1", y="eta2", eta3=0).save(
> ...     "phi.gif", writer="pillow"
> ... )
> >>> # complex amplitudes over mode numbers m, n
> >>> modes = phi.plasma.analysis.mode_spectrum()
> >>> # the strongest (m, n) over time
> >>> phi.plasma.plot.mode_amplitudes(top=4, fit=True)
> >>> # guiding-center orbits, by orbit class
> >>> out.kinetic_ions.orbits.plasma.plot.poloidal()

> **Selecting what to show**
>
> Name every dimension a plot doesn't draw: an integer is a position (``t=-1`` the last, ``t=0`` the
> first) and a float the nearest coordinate value (``t=0.35``). Slices draw two dimensions: logical
> ones (``x="eta1", y="eta2"``), or physical ones with ``coords="physical", plane="XY"`` (or
> ``"XZ"``, ``"YZ"``, ``"RZ"``). ``array.plasma.data.<plot>(...)`` returns the selected data a plot
> would draw, instead of the figure.

> **What is where**
>
> * Time series and rates: ``plot.timeseries(fit=(t0, t1), reference=...)``,
>   ``analysis.growth_rate``, ``analysis.damping_rate``, ``analysis.envelope``,
>   ``analysis.oscillation_frequency`` (from zero crossings).
> * Profiles: ``plot.lineout``, ``plot.profiles``, ``plot.line_animation`` (``alongside=`` for
>   panels in sync), each with ``reference=`` for exact solutions; ``analysis.map_coordinate`` for
>   physical coordinates (``eta1`` → ``r`` in m).
> * Several plots in one figure: ``with plasma_plots.figure(2, 1) as fig:`` and ``ax=fig[0]``,
>   ``ax=fig[1]``.
> * 2-D fields: ``plot.slice``, ``plot.panels``, ``plot.animation``, ``plot.viewer``,
>   ``plot.frames``, with ``levels=`` (contour lines), ``overlays=`` (a second field's contours,
>   boundary, lines, points), ``symmetric=``, ``robust=``; ``plot.vector`` for vector fields.
> * 3-D views (``pip install "plasma-plots[pyvista]"``): ``plot.isosurface``, ``plot.slices_3d``,
>   ``plot.glyphs``, ``plot.streamlines``, ``plot.movie``; ``data.to_vtk`` for ParaView.
> * Spectra: ``analysis.time_fft``, ``analysis.spectral_peaks``, ``analysis.filter_time``,
>   ``analysis.mode_spectrum``, ``analysis.matrix_pencil``, ``analysis.cross_spectrum``;
>   ``plot.power_spectrum``, ``plot.spectrogram``, ``plot.mode_amplitudes``, ``plot.mode_profiles``.
> * Dispersion relations: ``plot.dispersion(branches=...)``, ``analysis.dispersion`` and
>   ``.plasma.analysis.trace_branch(theory)`` on the spectrum.
> * Comparing with theory: ``analysis.error(exact)``, ``plot.against_theory(theory)``,
>   ``analysis.project_mode``; convergence studies with ``plot.convergence``.
> * Vector calculus on mapped domains: ``analysis.gradient``, ``analysis.divergence``,
>   ``analysis.curl``, ``analysis.flux_function``, ``analysis.toroidal_components``.
> * Magnetic topology: ``analysis.critical_points`` (O- and X-points of a flux function),
>   ``analysis.reconnected_flux``, ``plot.critical_points``.
> * Field lines: ``analysis.field_lines`` traces them through the mapped grid; the lines Dataset
>   has ``dataset.plasma.plot.poincare`` (``islands=True`` labels the island chains),
>   ``.field_lines``, ``.footprint``, ``.connection_length`` and
>   ``dataset.plasma.analysis.poincare_section``, ``.classify_field_lines``, ``.islands``,
>   ``.footprint``, ``.seed_grid``; a field along the lines with ``analysis.sample_along``,
>   ``analysis.parallel_wavenumber`` and ``plot.along_field_lines``; a flux surface unfolded with
>   ``plot.surface_map`` (straight field lines of slope ι); ``plot.poincare`` traces and plots
>   in one go.
> * Particles: ``dataset.plasma.plot.scatter``, ``.animation`` (``trail=``, ``paths=``,
>   ``color="classification"``), ``.paths``, ``.poloidal``,
>   ``.orbit_grid``, ``.orbit_classification``, ``.orbits_3d``; ``dataset.plasma.analysis.
>   classify_orbits``, ``.orbit_invariants``, ``.bounce_period``; binned distributions with
>   ``plot.slice(x="eta1", y="v1")`` and ``analysis.velocity_moments``.
> * δf and losses: ``dataset.plasma.plot.weight_histogram``, ``.marker_density`` (sampling
>   against physical density), ``.lost_fraction``, ``.loss_map`` (initial phase space colored
>   by loss time); ``dataset.plasma.analysis.weight_statistics`` (noise, effective markers),
>   ``.marker_density``, ``.lost_fraction``, ``.loss_map``.
> * Whole runs: ``out.plot.energies``, ``out.plot.scalars``, ``out.plot.equilibrium``,
>   ``out.plot.profile``; ``out.analysis.linear_mhd_energies``, ``out.analysis.time_fft``,
>   ``out.analysis.mode_spectrum``.
> * Integrals: ``plasma_plots.analysis.volume_integral`` and ``field_energy``;
>   ``analysis.surface_average`` for flux-surface averages.
> * GVEC equilibria: ``.plasma`` reads ``state.evaluate(...)`` itself, ``plasma_plots.from_gvec(ds)``
>   attaches the geometry to every variable; poloidal planes with
>   ``overlays={"coordinate_lines": {"rho": 4, "theta_P": 8}}`` (and ``plane="X1X2"``), ι with
>   ``plot.lineout(rationals=4)`` and ``analysis.rational_surfaces``; on a Boozer grid
>   ``analysis.boozer_spectrum``, ``analysis.quasisymmetry_error`` and ``plot.boozer_spectrum``.
> * DESC equilibria: ``plasma_plots.from_desc(eq, ["|B|", "iota", "sqrt(g)"], rho=11, theta=64,
>   zeta=40)`` evaluates them into the same flux-coordinate Datasets (``sfl="pest"`` for the PEST
>   angle ``theta_P``); DESC's names stay, ``ev["|B|"].plasma.plot...``.
> * Analytic theory to compare with (plain functions, not accessors): ``plasma_plots.theory.kinetic``
>   (Landau damping, beam instabilities, Weibel), ``.waves`` (MHD, Hall-MHD and cold-plasma waves,
>   drift waves, continua), ``.parameters`` (plasma parameters, Struphy's units), ``.orbits``,
>   ``.exact`` (Riemann problem, dam break, diffusion, ...) and ``.numerics`` (time-integrator and
>   discretization errors). Their functions go straight into ``branches=``, ``reference=`` and
>   ``theory=``, e.g. ``phi.plasma.plot.dispersion(branches={"kinetic": kinetic.langmuir})``.
> 
> Plots return a ``PlotResult`` (``.fig``, ``.ax``, ``.save(path)``, ``.show()``); animations a
> ``matplotlib.animation.FuncAnimation`` (keep a reference; ``.save("a.gif", writer="pillow")``); 3-D
> views a ``pyvista.Plotter`` (``.show()``, ``.screenshot(path)``; ``pyvista.OFF_SCREEN = True`` in
> scripts). Analysis methods return labeled xarray objects, which have ``.plasma`` in turn.
> 
> Every Matplotlib plot also draws as an interactive Plotly figure (``pip install
> "plasma-plots[plotly]"``): ``phi.plasma.plot.slice(t=-1, eta3=0, backend="plotly")``, or
> ``plasma_plots.set_backend("plotly")`` for all of them. The result is a ``PlotResult`` too
> (``.save("phi.html")``); animations and viewers get a slider. See
> [`plasma_plots.plotly_backend`][plasma_plots.plotly_backend]. For a paper, ``backend="tikz"`` (``pip install
> "plasma-plots[tikz]"``) converts the plot into TikZ/pgfplots code with LaTeX text
> (``.save("phi.tex")``, ``.tikz``, ``.pdf``); see [`plasma_plots.tikz_backend`][plasma_plots.tikz_backend].
> 
> Under MPI (``mpirun -n 4 python script.py``), plots are drawn and saved on rank 0 only; the other
> ranks get a ``SkippedPlot`` whose methods do nothing, so one script runs unchanged in serial and
> in parallel. Analysis runs on every rank. See [`plasma_plots.mpi`][plasma_plots.mpi].
> 
> The functions behind the accessors, for plain ``xarray.DataArray`` input, are in
> ``plasma_plots.plotting``, ``.analysis``, ``.spectral``, ``.spectral_plots``, ``.fieldlines``,
> ``.fieldline_plots``, ``.pyvista_plots`` and ``.arrays``.
> 
> Guides and the full reference: https://max-models.github.io/plasma-plots (for language models:
> https://max-models.github.io/plasma-plots/llms.txt).

> **Example**
>
> Runs as is, on synthetic data:
> 
> >>> import matplotlib
> >>> matplotlib.use("Agg")
> >>> import numpy as np
> >>> import xarray as xr
> >>> import plasma_plots
> >>> t = np.linspace(0.0, 20.0, 201)
> >>> x = np.linspace(0.0, 1.0, 64, endpoint=False)
> >>> phi = xr.DataArray(
> ...     0.01 * np.exp(0.1 * t)[:, None] * np.sin(2 * np.pi * x)[None],
> ...     dims=("t", "eta1"),
> ...     coords={"t": t, "eta1": x},
> ...     name="phi",
> ... )
> >>> # the k = 1 amplitude over t
> >>> amplitude = phi.plasma.analysis.project_mode(dim="eta1", number=1)
> >>> round(
> ...     float(amplitude.plasma.analysis.growth_rate(window=(5.0, 20.0)).rate),
> ...     3,
> ... )
> 0.1
> >>> result = phi.plasma.plot.slice(x="eta1", y="t")  # a space-time map
> >>> type(result).__name__
> 'PlotResult'

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/__init__.py#L1-L1)

## plasma_plots.PlasmaAccessor

*class*

```python
class PlasmaAccessor
```

Re-exported from: `plasma_plots.accessors`

Struphy diagnostics of one array: ``array.plasma.plot``, ``.analysis`` and ``.data``.

Registered on every ``xarray.DataArray`` when ``plasma_plots`` is imported. A GVEC
evaluation is read in plasma-plots' conventions first (see [`plasma_plots.gvec.from_gvec()`][plasma_plots.gvec.from_gvec]).

**Examples**

```pycon
>>> import plasma_plots
>>> phi.plasma.plot.slice(x="eta1", y="eta2", t=-1)
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/accessors.py#L4179-L4213)

### plasma_plots.PlasmaAccessor.analysis

*property*

```python
analysis: 'ArrayAnalysis'
```

Diagnostics of this array, e.g. ``array.plasma.analysis.growth_rate()``.

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/accessors.py#L4203-L4205)

### plasma_plots.PlasmaAccessor.data

*property*

```python
data: 'ArrayData'
```

The data behind each plot, without rendering it.

E.g. for a different plotting library: ``array.plasma.data.slice(x="eta1", y="v1", t=-1)``.

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/accessors.py#L4208-L4213)

### plasma_plots.PlasmaAccessor.plot

*property*

```python
plot: 'ArrayPlots'
```

Plots of this array, e.g. ``array.plasma.plot.slice(x="eta1", y="v1", t=-1)``.

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/accessors.py#L4198-L4200)

## plasma_plots.SkippedPlot

*class*

```python
class SkippedPlot
```

Re-exported from: `plasma_plots.mpi`

What a plot returns on MPI ranks other than 0, where nothing is drawn.

Any public attribute or call returns the same object, so ``plot(...).save(path)``,
``plotter.show()`` or ``animation.save(path)`` run on every rank but act only on rank 0. It is
false and iterates as empty, like the (empty) list of files it wrote.

**Parameters**

- `name` (`str`) — The skipped plot function, shown by ``repr``.
- `rank` (`int`) — The rank that skipped it.

**Examples**

```pycon
>>> skipped = SkippedPlot("plot_slice", rank=1)
>>> skipped.save("slice.png").fig is skipped  # nothing is written
True
>>> list(skipped), bool(skipped)
([], False)
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/mpi.py#L91-L148)

## plasma_plots.figure

*function*

```python
def figure(nrows: int = 1, ncols: int = 1, *, backend: str | None = None, sharex: bool | str = False, sharey: bool | str = False, figsize=None, title: str | None = None, **options) -> Figure
```

Re-exported from: `plasma_plots.figures`

Compose several plots into one figure, drawn with Matplotlib, or as one Plotly or TikZ figure.

Use it as a ``with`` block: every plot method given ``ax=fig[i]`` draws into panel ``i``;
at the end of the block the figure is finished, and with ``backend="plotly"`` or
``backend="tikz"`` converted once.

**Parameters**

- `nrows` (`int`) (default: `1`) — The number of rows of panels. Default: 1.
- `ncols` (`int`) (default: `1`) — The number of columns of panels. Default: 1.
- `backend` (`('matplotlib', 'plotly', 'tikz')`) (default: `"matplotlib"`) — Finish the figure as a Matplotlib figure, as an interactive Plotly figure, or as a TikZ/pgfplots figure for LaTeX. Default: the one set with [`plasma_plots.set_backend()`][plasma_plots.plotly_backend.set_backend], ``"matplotlib"`` unless changed.
- `sharex` (`bool or {'row', 'col', 'all'}`) (default: `False`) — Share the horizontal axes (and their zoom, in Plotly), as for ``matplotlib.pyplot.subplots``. Default: ``False``.
- `sharey` (`bool or {'row', 'col', 'all'}`) (default: `False`) — Share the vertical axes. Default: ``False``.
- `figsize` (`(float, float)`) (default: `None`) — The size in inches. Default: from the number of panels.
- `title` (`str`) (default: `None`) — A title above all panels. Default: none.
- `**options` (default: `{}`) — Passed on to ``matplotlib.pyplot.subplots``, e.g. ``gridspec_kw={"height_ratios": [3, 1]}``.

**Returns**

- (`Figure`) — The figure; index it for the panels' axes, and save or show it after the block.

**Examples**

```pycon
>>> with plasma_plots.figure(2, 1, sharex=True, backend="plotly") as fig:
...     energy.plasma.plot.timeseries(fit=(0.0, 5.0), ax=fig[0])
...     drift.plasma.plot.timeseries(logy=True, ax=fig[1])
>>> fig.save("energies.html")
>>> fig.results[0].fit_results[0].rate
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/figures.py#L228-L289)

## plasma_plots.from_desc

*function*

```python
def from_desc(eq, names: str | Sequence[str], *, rho: int | float | Sequence[float] = 11, theta: int | float | Sequence[float] = 32, zeta: int | float | Sequence[float] = 24, sfl: str | None = None) -> xr.Dataset
```

Re-exported from: `plasma_plots.desc`

Evaluate DESC quantities on a grid, as a Dataset in plasma-plots' conventions.

See [`plasma_plots.desc`][plasma_plots.desc] for what the Dataset contains.

**Parameters**

- `eq` (`desc.equilibrium.Equilibrium`) — The equilibrium, e.g. ``desc.io.load("eq.h5")`` or ``desc.examples.get("W7-X")``.
- `names` (`str or sequence of str`) — DESC's names of the quantities (``"|B|"``, ``"iota"``, ``"sqrt(g)"``, ``"B"``, ``"J"``, ``"p"``, ``"D_Mercier"``, ``"V"``, ...; see DESC's list of variables). ``"theta_PEST"`` becomes the coordinate ``theta_P``. Quantities that are not scalars, profiles, fields or 3-vectors (such as ``"grad(B)"``) are not supported, nor ``"x"``: the position is ``X``, ``Y``, ``Z``.
- `rho` (`int, float or sequence of float`) (default: `11`) — The flux surfaces: an integer as that many points from 0 to 1, a float or a sequence as the values. Default: 11 points.
- `theta` (`int, float or sequence of float`) (default: `32`) — The poloidal angles (PEST angles with ``sfl="pest"``): an integer as that many points over ``[0, 2π)``, else the values. Default: 32 points.
- `zeta` (`int, float or sequence of float`) (default: `24`) — The toroidal angles: an integer as that many points over one field period ``[0, 2π/nfp)``, else the values, which may cover the whole torus. Default: 24 points.
- `sfl` (`(None, 'pest')`) (default: `None`) — ``"pest"`` for a grid in the straight-field-line PEST angle ``theta_P`` (DESC's own ``theta`` becomes a coordinate, found by ``eq.map_coordinates``) rather than in DESC's poloidal angle. Default: ``None``.

**Returns**

- (`xarray.Dataset`) — The quantities over ``rho``, ``theta`` (or ``theta_P``) and ``zeta``, vectors along ``component``, with the coordinates ``X``, ``Y``, ``Z``, labels, units, the angles' ``period`` and the ``nfp`` attribute.

**Raises**

- `ValueError` — For an unknown ``sfl``, a name DESC doesn't know, a quantity of an unsupported shape or a
coordinate triplet such as ``"x"``.

**Examples**

```pycon
>>> ev = from_desc(eq, ["|B|", "iota", "sqrt(g)"], rho=11, theta=64, zeta=40)
>>> ev["|B|"].plasma.plot.panels(
...     sweep="zeta", coords="physical", plane="RZ", nrows=1, ncols=3
... )
>>> pest = from_desc(eq, "|B|", rho=[0.5], theta=64, zeta=48, sfl="pest")
>>> pest["|B|"].plasma.plot.slice(x="zeta", y="theta_P", rho=0.5)
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/desc.py#L108-L313)

## plasma_plots.from_gvec

*function*

```python
def from_gvec(data: xr.DataArray | xr.Dataset, *, nfp: int | None = None) -> xr.DataArray | xr.Dataset
```

Re-exported from: `plasma_plots.gvec`

Return GVEC evaluations in plasma-plots' conventions; see [`plasma_plots.gvec`][plasma_plots.gvec].

Data that is not in GVEC's layout (see [`is_gvec()`][plasma_plots.gvec.is_gvec]) comes back with only the steps that
apply, so calling it twice is harmless.

**Parameters**

- `data` (`xarray.Dataset or xarray.DataArray`) — A Dataset from ``state.evaluate(...)``, ``state.evaluate_sfl(...)`` or ``gvec.Evaluations`` (also after ``to_netcdf`` and ``open_dataset``), or one of its variables.
- `nfp` (`int`) (default: `None`) — The number of field periods. Default: the ``N_FP`` variable (evaluate ``"N_FP"`` with the rest), else an ``nfp`` attribute, else from a toroidal grid that spans one field period uniformly (GVEC's default grid); without one, the toroidal angle gets no ``period``.

**Returns**

- (`xarray.Dataset or xarray.DataArray`) — The same data, of the same type, with the dimensions ``rho``, ``theta``/``theta_B``/ ``theta_P``, ``zeta``/``zeta_B`` and ``component``, the coordinates ``X``, ``Y``, ``Z`` (from ``pos``), ``X1``, ``X2``, labels from the ``symbol`` attributes, the angles' ``period`` and the ``nfp`` attribute.

**Examples**

```pycon
>>> ev = from_gvec(
...     state.evaluate("mod_B", "pos", "N_FP", rho=11, theta=32, zeta=24)
... )
>>> ev.mod_B.plasma.plot.panels(
...     sweep="zeta", coords="physical", plane="RZ", nrows=1, ncols=3
... )
>>> boozer = from_gvec(
...     state.evaluate_sfl(
...         "mod_B", "pos", rho=[0.5], theta=32, zeta=24, sfl="boozer"
...     )
... )
>>> boozer.mod_B.plasma.plot.slice(x="zeta_B", y="theta_B", rho=0.5)
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/gvec.py#L122-L225)

## plasma_plots.get_backend

*function*

```python
def get_backend() -> str
```

Re-exported from: `plasma_plots.plotly_backend`

Return the backend of plots that do not pass ``backend=`` themselves.

**Returns**

- (`str`) — ``"matplotlib"`` (the default), ``"plotly"`` or ``"tikz"``.

> **See Also**
>
> [`set_backend()`][plasma_plots.plotly_backend.set_backend] : Changes it.

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/plotly_backend.py#L103-L115)

## plasma_plots.is_plotting_rank

*function*

```python
def is_plotting_rank() -> bool
```

Re-exported from: `plasma_plots.mpi`

Tell whether this process draws plots and writes their files.

**Returns**

- (`bool`) — ``True`` on rank 0 of an MPI job and in any process outside one.

> **See Also**
>
> [`mpi_rank()`][plasma_plots.mpi.mpi_rank] : The rank this is decided from.

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/mpi.py#L76-L88)

## plasma_plots.mpi_rank

*function*

```python
def mpi_rank() -> int
```

Re-exported from: `plasma_plots.mpi`

Return this process' rank in ``MPI_COMM_WORLD``, without initializing MPI.

The rank comes from ``mpi4py`` if the application has already initialized MPI, otherwise from
the per-rank variables MPI launchers export. ``STRUPHY_MPI=0`` makes every process rank 0.

**Returns**

- (`int`) — The rank, or 0 outside an MPI job.

**Examples**

```pycon
>>> mpi_rank()  # in a serial run
0
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/mpi.py#L38-L73)

## plasma_plots.save_figure

*function*

```python
def save_figure(figure, name, *, formats=('html', 'png', 'plotly.json'), show: bool = False, frame: int | None = None, still=None, width: int = 1100, height: int = 650, scale: float = 2.0) -> list[str]
```

Re-exported from: `plasma_plots.plotting`

Save a figure in several formats at once, as ``<name>.<format>``.

The defaults write an interactive page, an image and the figure JSON of a Plotly figure: the
three files a web page needs. Like [`PlotResult.save()`][plasma_plots.plotting.PlotResult.save], this does nothing on MPI ranks
other than 0.

**Parameters**

- `figure` (`(PlotResult, Figure, plotly.graph_objects.Figure or matplotlib.figure.Figure)`) — What to save: the result of a plot (e.g. with ``backend="plotly"``), a figure of [`plasma_plots.figure()`][plasma_plots.figures.figure], or a figure made some other way.
- `name` (`str or pathlib.Path`) — The files' path without the format, e.g. ``"maxwell-wave"`` or ``"figures/energy"``.
- `formats` (`sequence of str`) (default: `('html', 'png', 'plotly.json')`) — The formats, each appended to ``name`` after a dot; the last extension picks how it is written, as in [`PlotResult.save()`][plasma_plots.plotting.PlotResult.save]. Default: ``("html", "png", "plotly.json")``.
- `show` (`bool`) (default: `False`) — Show the figure first. Default: ``False``.
- `frame` (`int`) (default: `None`) — For the image of a Plotly animation: the frame it shows (e.g. ``-1`` for the last). The page and the JSON keep the whole animation. Default: the first frame.
- `still` (`plotly.graph_objects.Figure`) (default: `None`) — A figure that the image shows instead, e.g. a view of an animation that is none of its frames. The page and the JSON keep ``figure``.
- `width` (`int`) (default: `1100`) — Width of an image of a Plotly figure, in layout pixels. Default: 1100.
- `height` (`int`) (default: `650`) — Height of an image of a Plotly figure, in layout pixels. Default: 650.
- `scale` (`float`) (default: `2.0`) — Resolution factor of an image of a Plotly figure. Default: 2.

**Returns**

- (`list of str`) — The paths written; empty on MPI ranks other than 0.

> **See Also**
>
> [`PlotResult.save()`][plasma_plots.plotting.PlotResult.save] : Save one file.

**Examples**

```pycon
>>> dispersion = spectrum.plasma.plot.dispersion(kmin=0, backend="plotly")
>>> # maxwell-wave.html, .png, .plotly.json
>>> save_figure(dispersion, "maxwell-wave", show=True)
>>> save_figure(movie, "phase-space", frame=len(movie.fig.frames) // 2)
>>> save_figure(
...     go.Figure(go.Scatter(x=t, y=energy)), "energy", formats=("html",)
... )
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/plotting.py#L424-L511)

## plasma_plots.set_backend

*function*

```python
def set_backend(backend: str) -> str
```

Re-exported from: `plasma_plots.plotly_backend`

Set the backend of every plot that does not pass ``backend=`` itself.

**Parameters**

- `backend` (`('matplotlib', 'plotly', 'tikz')`) (default: `"matplotlib"`) — The new default (``"tikz"``: see [`plasma_plots.tikz_backend`][plasma_plots.tikz_backend]).

**Returns**

- (`str`) — The previous default, e.g. to restore it afterwards.

**Raises**

- `ValueError` — If ``backend`` is not ``"matplotlib"``, ``"plotly"`` or ``"tikz"``.

> **See Also**
>
> [`get_backend()`][plasma_plots.plotly_backend.get_backend] : The current default.

**Examples**

```pycon
>>> previous = plasma_plots.set_backend("plotly")
>>> phi.plasma.plot.slice(t=-1, eta3=0)  # a Plotly figure
>>> plasma_plots.set_backend(previous)
```

[View source](https://github.com/max-models/plasma-plots/blob/devel/src/plasma_plots/plotly_backend.py#L70-L100)

**Modules**

- `plasma_plots.accessors`
- `plasma_plots.analysis`
- `plasma_plots.arrays`
- `plasma_plots.cli`
- `plasma_plots.desc`
- `plasma_plots.fieldline_plots`
- `plasma_plots.fieldlines`
- `plasma_plots.figures`
- `plasma_plots.gallery`
- `plasma_plots.gvec`
- `plasma_plots.mpi`
- `plasma_plots.output_accessors`
- `plasma_plots.plotly_backend`
- `plasma_plots.plotting`
- `plasma_plots.pyvista_plots`
- `plasma_plots.spectral`
- `plasma_plots.spectral_plots`
- `plasma_plots.theory`
- `plasma_plots.tikz_backend`
