# plasma_plots.pyvista_plots

*module*

Three-dimensional PyVista views of labeled Struphy output.

Every function returns a ``pyvista.Plotter`` without showing it: call ``.show()`` in an
interactive session or notebook, or ``.screenshot(path)`` (with ``pyvista.OFF_SCREEN = True``
in batch jobs). Pass ``plotter=`` to draw several views into one scene.

Fields need their physical ``X``, ``Y``, ``Z`` coordinates attached, as every Struphy field
product has; orbits need physical positions ``x``, ``y``, ``z``.

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

## plasma_plots.pyvista_plots.ORBIT_CLASS_COLORS

*attribute* · *module attribute*

```python
ORBIT_CLASS_COLORS = {'passing': 'tab:blue', 'trapped': 'tab:orange', 'lost': 'grey'}
```

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

## plasma_plots.pyvista_plots.RENDERERS

*attribute* · *module attribute*

```python
RENDERERS = {'isosurface': pyvista_isosurface, 'slices': pyvista_slices, 'glyphs': pyvista_glyphs, 'streamlines': pyvista_streamlines}
```

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

## plasma_plots.pyvista_plots.boundary_faces

*function*

```python
def boundary_faces(points: np.ndarray) -> list[np.ndarray]
```

Return the real boundary faces of a point array, see [`boundary_keys()`][plasma_plots.pyvista_plots.boundary_keys].

**Parameters**

- `points` (`numpy.ndarray`) — Physical points, shape ``(n1, n2, n3, 3)``.

**Returns**

- (`list of numpy.ndarray`) — The points of each face, with a size-one axis in the face's logical direction.

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

## plasma_plots.pyvista_plots.boundary_keys

*function*

```python
def boundary_keys(points: np.ndarray) -> list[tuple[int, int]]
```

Find the logical faces of a ``(n1, n2, n3, 3)`` point array that are real boundaries.

Faces that collapse to a line or point (a polar axis) and pairs of opposite faces that
coincide (the seam of a periodic direction, e.g. ``phi = 0`` of a full torus) are dropped.
A grid that is flat in one direction (a 2-D run) is its own single face.

**Parameters**

- `points` (`numpy.ndarray`) — Physical points, shape ``(n1, n2, n3, 3)``.

**Returns**

- (`list of (int, int)`) — ``(axis, index)`` of each boundary face; ``index`` is ``0`` or ``-1``.

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

## plasma_plots.pyvista_plots.is_flat

*function*

```python
def is_flat(grid) -> bool
```

Whether a structured grid has a single point in one logical direction (a 2-D run or cut).

**Parameters**

- `grid` (`pyvista.StructuredGrid`) — The grid, e.g. from [`structured_grid()`][plasma_plots.pyvista_plots.structured_grid].

**Returns**

- (`bool`) — ``True`` if any of the grid's dimensions is 1.

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

## plasma_plots.pyvista_plots.orbit_polylines

*function*

```python
def orbit_polylines(orbits: xr.Dataset, *, color_by: str = 't', max_markers: int = 200)
```

Marker orbits as one ``pyvista.PolyData`` line per marker, with point data ``color_by``.

Samples where a marker is lost (every quantity zero) are dropped. ``color_by`` is ``"t"``,
``"classification"`` (see [`classify_orbits()`][plasma_plots.analysis.classify_orbits]), or the name of
any ``(t, marker)`` variable, e.g. ``"v_par"`` or ``"weight"``.

**Parameters**

- `orbits` (`xarray.Dataset`) — Marker orbits with dims ``(t, marker)`` and physical positions ``x``, ``y``, ``z``.
- `color_by` (`str`) (default: `'t'`) — The point data to attach: ``"t"``, ``"classification"`` or a variable name. Default: ``"t"``.
- `max_markers` (`int`) (default: `200`) — At most this many markers are used. Default: 200.

**Returns**

- (`pyvista.PolyData`) — One line per marker with at least two samples left, and point data ``color_by``; empty if no marker has.

**Raises**

- `ValueError` — If ``color_by`` is neither ``"t"``, ``"classification"`` nor a variable of ``orbits``.

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

## plasma_plots.pyvista_plots.prepare_slices_3d

*function*

```python
def prepare_slices_3d(data: xr.DataArray, *, cuts: dict | None = None) -> list[xr.DataArray]
```

The logical cuts of a scalar ``(eta1, eta2, eta3)`` field that [`pyvista_slices()`][plasma_plots.pyvista_plots.pyvista_slices] draws.

``cuts`` maps ``eta1``/``eta2``/``eta3`` to one position or a list: a float is the nearest
logical coordinate, an integer a grid index (``-1`` the last). The default is
the middle of every dimension with more than one point, or for a 2-D field (one dimension
with a single point) the whole plane. Each cut keeps its size-one dimension, so it still
maps onto a surface in physical space.

**Parameters**

- `data` (`xarray.DataArray`) — A scalar field with dims ``(eta1, eta2, eta3)`` and physical coordinates ``X``, ``Y``, ``Z``.
- `cuts` (`dict`) (default: `None`) — ``{dim: position or list of positions}`` along ``eta1``, ``eta2``, ``eta3``.

**Returns**

- (`list of xarray.DataArray`) — One array per cut, in the order of ``cuts``.

**Raises**

- `ValueError` — If a cut is along another dimension.
- `TypeError` — If a position is neither an integer nor a float.

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

## plasma_plots.pyvista_plots.push_forward

*function*

```python
def push_forward(data: xr.DataArray) -> xr.DataArray
```

Cartesian components of a vector field given by contravariant logical components.

``data`` has dims ``(component, eta1, eta2, eta3)``, e.g. from
``out.evaluate(name, eta1=..., eta2=..., eta3=..., representation="v")``. The Cartesian
field is ``sum_i v^i dX/de_i``, with the Jacobian of the mapping differentiated numerically
from the attached ``X``, ``Y``, ``Z`` coordinates, so every logical direction needs at least
two points.

**Parameters**

- `data` (`xarray.DataArray`) — The vector field, dims ``(component, eta1, eta2, eta3)`` with three contravariant components and physical coordinates ``X``, ``Y``, ``Z``.

**Returns**

- (`xarray.DataArray`) — The Cartesian ``x``, ``y``, ``z`` components, same dims and coordinates, labeled ``"<label> (Cartesian)"``.

**Raises**

- `ValueError` — If the field doesn't have three components or a logical direction has fewer than two
points.

**Examples**

```pycon
>>> B_xyz = push_forward(
...     out.evaluate("em_fields/b_field", representation="v").isel(t=-1)
... )
```

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

## plasma_plots.pyvista_plots.pyvista_domain

*function*

```python
def pyvista_domain(domain, *, n1: int = 8, n2: int = 32, n3: int = 32, resolution: int = 4, color='black', surface: bool = True, cross_section: bool = True, title: str | None = None, plotter=None)
```

The mapping of a Struphy ``domain`` as a wireframe of logical grid lines.

Grid lines are drawn on the real boundary faces (see [`boundary_keys()`][plasma_plots.pyvista_plots.boundary_keys]; for a torus the
outer and inner surfaces) and, with ``cross_section``, over the whole ``eta3 = 0`` face (for a
torus a poloidal cross-section). ``n1``, ``n2``, ``n3`` lines per direction, each sampled
``resolution`` times finer so curved lines stay smooth. ``surface`` adds the translucent
boundary. Useful to check the geometry (and its orientation) of a run; ``n3=1`` shows a
2-D run's plane.

**Parameters**

- `domain` (`callable`) — A Struphy domain (mapping), e.g. ``out.domain``, called as ``domain(eta1, eta2, eta3, squeeze_out=False)`` to give ``x``, ``y``, ``z``.
- `n1` (`int`) (default: `8`) — Grid lines along ``eta1``. Default: 8.
- `n2` (`int`) (default: `32`) — Grid lines along ``eta2``. Default: 32.
- `n3` (`int`) (default: `32`) — Grid lines along ``eta3``. Default: 32.
- `resolution` (`int`) (default: `4`) — Samples per line spacing, so curved lines stay smooth. Default: 4.
- `color` (`str`) (default: `'black'`) — Color of the grid lines. Default: ``"black"``.
- `surface` (`bool`) (default: `True`) — Draw the translucent boundary surface. Default: ``True``.
- `cross_section` (`bool`) (default: `True`) — Also draw grid lines over the ``eta3 = 0`` face. Default: ``True``.
- `title` (`str`) (default: `None`) — Text in the scene's corner. Default: ``"Domain"``; ``""`` for none.
- `plotter` (`pyvista.Plotter`) (default: `None`) — Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the domain when this function creates the plotter.

**Returns**

- (`pyvista.Plotter`) — The scene, not yet shown: call ``.show()`` or ``.screenshot(path)``.

**Examples**

```pycon
>>> pyvista_domain(out.domain).show()
>>> pyvista_domain(out.domain, n3=1, surface=False).show()
```

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

## plasma_plots.pyvista_plots.pyvista_glyphs

*function*

```python
def pyvista_glyphs(data: xr.DataArray, *, components: Literal['cartesian', 'contravariant'] = 'cartesian', stride: int = 2, scale: float | None = None, cmap='viridis', show_domain: bool = True, title: str | None = None, plotter=None)
```

Arrows of a selected ``(component, eta1, eta2, eta3)`` vector field, colored by magnitude.

``components="cartesian"`` (default) for x/y/z components, e.g. a ``*_phy`` product;
``"contravariant"`` for logical components, pushed forward by [`push_forward()`][plasma_plots.pyvista_plots.push_forward].
``stride`` thins the grid in every direction; ``scale`` is the arrow length of the largest
vector (default: a tenth of the domain size).

**Parameters**

- `data` (`xarray.DataArray`) — A vector field with dims ``(component, eta1, eta2, eta3)`` (three components, every other dimension selected) and physical coordinates ``X``, ``Y``, ``Z``.
- `components` (`('cartesian', 'contravariant')`) (default: `"cartesian"`) — How to read the components: Cartesian x/y/z, or contravariant logical components, pushed forward. Default: ``"cartesian"``.
- `stride` (`int`) (default: `2`) — Draw an arrow at every ``stride``-th point in every direction. Default: 2.
- `scale` (`float`) (default: `None`) — Arrow length of the largest vector, in physical units. Default: a tenth of the domain size.
- `cmap` (`str or matplotlib colormap`) (default: `'viridis'`) — The colormap of the magnitude. Default: ``"viridis"``.
- `show_domain` (`bool`) (default: `True`) — Draw the domain's outer surface translucently for context. Default: ``True``.
- `title` (`str`) (default: `None`) — Text in the scene's corner. Default: the field's label; ``""`` for none.
- `plotter` (`pyvista.Plotter`) (default: `None`) — Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter.

**Returns**

- (`pyvista.Plotter`) — The scene, not yet shown: call ``.show()`` or ``.screenshot(path)``.

**Raises**

- `ValueError` — If ``stride`` is less than 1, ``components`` is unknown, or the field isn't a
three-component field over the logical dimensions with physical coordinates.

> **See Also**
>
> [`pyvista_streamlines()`][plasma_plots.pyvista_plots.pyvista_streamlines] : Field lines of the vector field.
> [`push_forward()`][plasma_plots.pyvista_plots.push_forward] : Cartesian components from contravariant ones.

**Examples**

```pycon
>>> pyvista_glyphs(B.isel(t=-1), stride=3).show()
>>> pyvista_glyphs(B.isel(t=-1), components="contravariant", scale=0.2).show()
```

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

## plasma_plots.pyvista_plots.pyvista_isosurface

*function*

```python
def pyvista_isosurface(data: xr.DataArray, *, values: int | list[float] = 5, cmap='viridis', opacity: float = 1.0, clim=None, show_domain: bool = True, title: str | None = None, symmetric: bool = False, robust: bool = False, plotter=None)
```

Contour surfaces of a selected scalar ``(eta1, eta2, eta3)`` field in physical space.

``values`` is the number of evenly spaced levels, or explicit levels (between the color
limits, which ``symmetric``/``robust`` set as in [`color_limits()`][plasma_plots.plotting.color_limits]).
``show_domain`` draws
the domain's outer surface translucently for context. For a 2-D field (one logical
direction with a single point) the levels are contour lines over the colored plane.

**Parameters**

- `data` (`xarray.DataArray`) — A scalar field with dims ``(eta1, eta2, eta3)`` (every other dimension selected; one logical dimension may be selected away) and physical coordinates ``X``, ``Y``, ``Z``.
- `values` (`int or list of float`) (default: `5`) — The number of evenly spaced levels strictly between the color limits, or explicit levels. Default: 5.
- `cmap` (`str or matplotlib colormap`) (default: `'viridis'`) — The colormap. Default: ``"viridis"``.
- `opacity` (`float`) (default: `1.0`) — Opacity of the surfaces (of the plane for a 2-D field). Default: 1.
- `clim` (`(float, float)`) (default: `None`) — Color limits; default from the field's values, see ``symmetric`` and ``robust``.
- `show_domain` (`bool`) (default: `True`) — Draw the domain's outer surface translucently for context (3-D fields only). Default: ``True``.
- `title` (`str`) (default: `None`) — Text in the scene's corner. Default: the field's label; ``""`` for none.
- `symmetric` (`bool`) (default: `False`) — Color limits symmetric about zero. Default: ``False``.
- `robust` (`bool`) (default: `False`) — Color limits from percentiles instead of the extremes, so outliers don't wash out the colors. Default: ``False``.
- `plotter` (`pyvista.Plotter`) (default: `None`) — Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter.

**Returns**

- (`pyvista.Plotter`) — The scene, not yet shown: call ``.show()`` or ``.screenshot(path)``.

**Raises**

- `ValueError` — If other dimensions remain or the physical coordinates are missing.

> **See Also**
>
> [`pyvista_slices()`][plasma_plots.pyvista_plots.pyvista_slices] : The field on surfaces of constant logical coordinate.

**Examples**

```pycon
>>> pyvista_isosurface(phi.isel(t=-1), values=[-0.1, 0.1]).show()
>>> pyvista_isosurface(phi.isel(t=-1), symmetric=True, opacity=0.6).show()
```

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

## plasma_plots.pyvista_plots.pyvista_orbits

*function*

```python
def pyvista_orbits(orbits: xr.Dataset, *, color_by: str = 't', max_markers: int = 200, tube_radius: float | None = None, cmap=None, domain: xr.DataArray | None = None, title: str | None = None, plotter=None)
```

Marker orbits as 3-D lines (or tubes), colored by time, orbit class, or any variable.

``domain`` is any field with physical coordinates whose outer surface is drawn translucently
for context. See [`orbit_polylines()`][plasma_plots.pyvista_plots.orbit_polylines] for ``color_by``.

**Parameters**

- `orbits` (`xarray.Dataset`) — Marker orbits with dims ``(t, marker)`` and physical positions ``x``, ``y``, ``z``.
- `color_by` (`str`) (default: `'t'`) — ``"t"``, ``"classification"`` (passing, trapped, lost, with a legend) or the name of any ``(t, marker)`` variable, e.g. ``"v_par"``. Default: ``"t"``.
- `max_markers` (`int`) (default: `200`) — At most this many markers are drawn. Default: 200.
- `tube_radius` (`float`) (default: `None`) — Draw the orbits as tubes of this radius. Default: plain lines.
- `cmap` (`str or matplotlib colormap`) (default: `None`) — The colormap (not used for ``"classification"``). Default: ``"viridis"``.
- `domain` (`xarray.DataArray`) (default: `None`) — A field with physical coordinates whose outer surface is drawn translucently; other than ``eta1``, ``eta2``, ``eta3``, its dimensions are taken at their first position.
- `title` (`str`) (default: `None`) — Text in the scene's corner. Default: ``"Marker orbits"``; ``""`` for none.
- `plotter` (`pyvista.Plotter`) (default: `None`) — Draw into this scene instead of a new one, to combine several views.

**Returns**

- (`pyvista.Plotter`) — The scene, not yet shown: call ``.show()`` or ``.screenshot(path)``.

**Raises**

- `ValueError` — If ``color_by`` is neither ``"t"``, ``"classification"`` nor a variable of ``orbits``.

> **See Also**
>
> [`orbit_polylines()`][plasma_plots.pyvista_plots.orbit_polylines] : The orbits as a ``pyvista.PolyData``, without drawing them.

**Examples**

```pycon
>>> pyvista_orbits(out.kinetic_ions.orbits, color_by="classification").show()
>>> pyvista_orbits(
...     out.kinetic_ions.orbits, color_by="v_par", domain=phi, tube_radius=0.01
... ).show()
```

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

## plasma_plots.pyvista_plots.pyvista_slices

*function*

```python
def pyvista_slices(data: xr.DataArray, *, cuts: dict | None = None, cmap='viridis', clim=None, show_domain: bool = True, title: str | None = None, symmetric: bool = False, robust: bool = False, plotter=None)
```

Surfaces of constant logical coordinate through a scalar field, drawn in physical space.

On a mapped domain these are the natural cuts: ``cuts={"eta3": [0, 0.25]}`` gives poloidal
cross-sections of a torus, ``cuts={"eta1": 0.8}`` the field on one flux surface. See
[`prepare_slices_3d()`][plasma_plots.pyvista_plots.prepare_slices_3d] for ``cuts``; color limits are shared by every cut.

**Parameters**

- `data` (`xarray.DataArray`) — A scalar field with dims ``(eta1, eta2, eta3)`` (every other dimension selected; one logical dimension may be selected away) and physical coordinates ``X``, ``Y``, ``Z``.
- `cuts` (`dict`) (default: `None`) — ``{dim: position or list of positions}`` along ``eta1``, ``eta2``, ``eta3``: a float is the nearest logical coordinate, an integer a grid index (``-1`` the last). Default: the middle of every dimension, or the whole plane of a 2-D field.
- `cmap` (`str or matplotlib colormap`) (default: `'viridis'`) — The colormap. Default: ``"viridis"``.
- `clim` (`(float, float)`) (default: `None`) — Color limits, shared by every cut; default from the whole field's values, see ``symmetric`` and ``robust``.
- `show_domain` (`bool`) (default: `True`) — Draw the domain's outer surface translucently for context. Not drawn when the whole plane of a 2-D field is shown. Default: ``True``.
- `title` (`str`) (default: `None`) — Text in the scene's corner. Default: the field's label; ``""`` for none.
- `symmetric` (`bool`) (default: `False`) — Color limits symmetric about zero. Default: ``False``.
- `robust` (`bool`) (default: `False`) — Color limits from percentiles instead of the extremes, so outliers don't wash out the colors. Default: ``False``.
- `plotter` (`pyvista.Plotter`) (default: `None`) — Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter.

**Returns**

- (`pyvista.Plotter`) — The scene, not yet shown: call ``.show()`` or ``.screenshot(path)``.

**Raises**

- `ValueError` — If other dimensions remain, the physical coordinates are missing, or a cut is along
another dimension.

> **See Also**
>
> [`prepare_slices_3d()`][plasma_plots.pyvista_plots.prepare_slices_3d] : The cuts, without drawing them.
> [`pyvista_isosurface()`][plasma_plots.pyvista_plots.pyvista_isosurface] : Contour surfaces of the field.

**Examples**

```pycon
>>> pyvista_slices(phi.isel(t=-1), cuts={"eta3": [0, 0.25]}).show()
>>> pyvista_slices(phi.isel(t=-1), cuts={"eta1": 0.8}, symmetric=True).show()
```

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

## plasma_plots.pyvista_plots.pyvista_streamlines

*function*

```python
def pyvista_streamlines(data: xr.DataArray, *, components: Literal['cartesian', 'contravariant'] = 'cartesian', n_points: int = 100, source_radius: float | None = None, source_center=None, max_length: float | None = None, tube_radius: float | None = None, cmap='viridis', show_domain: bool = True, title: str | None = None, plotter=None)
```

Field lines of a selected vector field, e.g. magnetic field lines, colored by magnitude.

Lines are traced in both directions from ``n_points`` seeds: by default grid points drawn
at random (reproducibly) across the domain, or, given ``source_center`` and/or
``source_radius``, points in that sphere (radius default: a quarter of the domain size;
center default: the bounding-box center, which can lie outside a curved domain). For a 2-D
field the lines stay on the plane (the out-of-plane component is ignored). See
[`pyvista_glyphs()`][plasma_plots.pyvista_plots.pyvista_glyphs] for ``components``.

**Parameters**

- `data` (`xarray.DataArray`) — A vector field with dims ``(component, eta1, eta2, eta3)`` (three components, every other dimension selected) and physical coordinates ``X``, ``Y``, ``Z``.
- `components` (`('cartesian', 'contravariant')`) (default: `"cartesian"`) — How to read the components: Cartesian x/y/z, or contravariant logical components, pushed forward by [`push_forward()`][plasma_plots.pyvista_plots.push_forward]. Default: ``"cartesian"``.
- `n_points` (`int`) (default: `100`) — The number of seed points (at most the number of grid points when seeding on the grid). Default: 100.
- `source_radius` (`float`) (default: `None`) — Seed in a sphere of this radius instead of at grid points. Default (when ``source_center`` is given): a quarter of the domain size.
- `source_center` (`(float, float, float)`) (default: `None`) — Seed in a sphere around this physical point instead of at grid points. Default (when ``source_radius`` is given): the bounding-box center.
- `max_length` (`float`) (default: `None`) — Maximum length of each line. Default: four times the domain size.
- `tube_radius` (`float`) (default: `None`) — Draw the lines as tubes of this radius. Default: plain lines.
- `cmap` (`str or matplotlib colormap`) (default: `'viridis'`) — The colormap of the magnitude. Default: ``"viridis"``.
- `show_domain` (`bool`) (default: `True`) — Draw the domain's outer surface translucently for context. Default: ``True``.
- `title` (`str`) (default: `None`) — Text in the scene's corner. Default: ``"<label> field lines"``; ``""`` for none.
- `plotter` (`pyvista.Plotter`) (default: `None`) — Draw into this scene instead of a new one, to combine several views. The camera is only aimed at the field when this function creates the plotter.

**Returns**

- (`pyvista.Plotter`) — The scene, not yet shown: call ``.show()`` or ``.screenshot(path)``.

**Raises**

- `ValueError` — If ``components`` is unknown, or the field isn't a three-component field over the
logical dimensions with physical coordinates.

> **See Also**
>
> [`pyvista_glyphs()`][plasma_plots.pyvista_plots.pyvista_glyphs] : Arrows of the vector field.

**Examples**

```pycon
>>> pyvista_streamlines(B.isel(t=-1)).show()
>>> pyvista_streamlines(
...     B.isel(t=-1),
...     source_center=(3.0, 0.0, 0.0),
...     source_radius=0.5,
...     tube_radius=0.01,
... ).show()
```

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

## plasma_plots.pyvista_plots.save_movie

*function*

```python
def save_movie(data: xr.DataArray, path, *, kind: Literal['isosurface', 'slices', 'glyphs', 'streamlines'] = 'slices', sweep: str = 't', step: int = 1, framerate: int = 10, clim=None, window_size=(1024, 768), **options)
```

Render one 3-D view per ``sweep`` step into a GIF (``.gif``) or video (``.mp4``, ...).

``kind`` picks the view and ``options`` are passed on to it. Scalar views share color
limits over the whole sweep (override with ``clim``), and the camera is fixed after the
first frame. GIFs need ``imageio``, videos ``imageio-ffmpeg``.

**Parameters**

- `data` (`xarray.DataArray`) — A field with dimension ``sweep`` plus what the view ``kind`` needs (see [`pyvista_slices()`][plasma_plots.pyvista_plots.pyvista_slices], [`pyvista_glyphs()`][plasma_plots.pyvista_plots.pyvista_glyphs], ...).
- `path` (`str or pathlib.Path`) — The output file: a GIF for ``.gif``, a video (e.g. ``.mp4``) for any other suffix.
- `kind` (`('isosurface', 'slices', 'glyphs', 'streamlines')`) (default: `"isosurface"`) — The view of each frame. Default: ``"slices"``.
- `sweep` (`str`) (default: `'t'`) — The dimension to step through, one frame per step. Default: ``"t"``.
- `step` (`int`) (default: `1`) — Use every ``step``-th position of ``sweep``. Default: 1.
- `framerate` (`int`) (default: `10`) — Frames per second. Default: 10.
- `clim` (`(float, float)`) (default: `None`) — Color limits of the scalar views (``"isosurface"``, ``"slices"``). Default: from the whole sweep, with the ``symmetric`` and ``robust`` options.
- `window_size` (`(int, int)`) (default: `(1024, 768)`) — Frame size in pixels. Default: ``(1024, 768)``.
- `**options` (default: `{}`) — Passed on to the view, e.g. ``cuts`` or ``cmap``. A ``title`` is prefixed to each frame's ``"<sweep> = <value>"`` label (default: the field's label).

**Returns**

- (`str`) — The path of the written file.

**Raises**

- `ValueError` — If ``kind`` is unknown, ``step`` isn't a positive integer, or ``data`` has no ``sweep``
dimension.

**Examples**

```pycon
>>> save_movie(phi, "phi.gif", cuts={"eta3": 0}, symmetric=True)
>>> save_movie(B, "B.mp4", kind="glyphs", step=2)
```

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

## plasma_plots.pyvista_plots.save_vtk

*function*

```python
def save_vtk(data: xr.DataArray, path, *, name: str | None = None) -> list[str]
```

Write a field to VTK structured grids (``.vts``) on its physical points, for ParaView.

With a ``t`` dimension, one file per time is written into the directory ``path``, plus a
``.pvd`` collection that ParaView opens as a time series; without one, ``path`` is a single
``.vts`` file. Vector fields (``component``) become point vectors. Any array works, e.g. a
[`filter_time()`][plasma_plots.spectral.filter_time] result, so filtered modes can be inspected in
ParaView too.

**Parameters**

- `data` (`xarray.DataArray`) — A field over ``(eta1, eta2, eta3)`` (and optionally ``t`` and ``component``) with physical coordinates ``X``, ``Y``, ``Z``; every other dimension selected.
- `path` (`str or pathlib.Path`) — The ``.vts`` file (the suffix is set to ``.vts``), or with a ``t`` dimension the directory to write into (created if needed).
- `name` (`str`) (default: `None`) — The name of the point data, also the file stem in a time series. Default: the field's label.

**Returns**

- (`list of str`) — The written paths: the ``.vts`` file, or the ``.pvd`` collection followed by one ``.vts`` file per time.

**Examples**

```pycon
>>> save_vtk(phi, "vtk/phi")
>>> save_vtk(phi.isel(t=-1), "phi_last.vts")
```

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

## plasma_plots.pyvista_plots.structured_grid

*function*

```python
def structured_grid(data: xr.DataArray, *, name: str | None = None)
```

A ``pyvista.StructuredGrid`` of a selected ``(eta1, eta2, eta3)`` field on its physical points.

A scalar field becomes point data ``name`` (default: the field's label); a vector field with
a ``component`` dimension of three Cartesian components becomes point vectors ``name``, plus
their magnitude as ``"|name|"``. Periodic directions are closed, see [`plasma_plots.arrays.close_periodic()`][plasma_plots.arrays.close_periodic].
The grid is useful directly for any PyVista filter.

**Parameters**

- `data` (`xarray.DataArray`) — A field with dims ``(eta1, eta2, eta3)`` (one of them may be selected away), or ``(component, eta1, eta2, eta3)`` for a vector field, and physical coordinates ``X``, ``Y``, ``Z``.
- `name` (`str`) (default: `None`) — The name of the point data. Default: the field's label.

**Returns**

- (`pyvista.StructuredGrid`) — The grid with the field as its active scalars or vectors.

**Raises**

- `ValueError` — If other dimensions remain, the physical coordinates are missing, fewer than two
logical dimensions are left, or a vector field doesn't have three components.

**Examples**

```pycon
>>> grid = structured_grid(phi.isel(t=-1))
>>> grid.contour([0.0]).plot()
```

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