5119 lines
154 KiB
Python
5119 lines
154 KiB
Python
|
|
"""Module containing pyvista wrappers for the vtk Charts API."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from collections.abc import Sequence
|
||
|
|
from functools import wraps
|
||
|
|
import inspect
|
||
|
|
import itertools
|
||
|
|
import re
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
from typing import ClassVar
|
||
|
|
import weakref
|
||
|
|
|
||
|
|
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
||
|
|
import matplotlib.pyplot as plt
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
import pyvista
|
||
|
|
from pyvista import vtk_version_info
|
||
|
|
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||
|
|
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||
|
|
from pyvista.core.utilities.misc import abstract_class
|
||
|
|
|
||
|
|
from . import _vtk
|
||
|
|
from .colors import COLOR_SCHEMES
|
||
|
|
from .colors import SCHEME_NAMES
|
||
|
|
from .colors import Color
|
||
|
|
from .colors import color_synonyms
|
||
|
|
from .colors import hexcolors
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from ._typing import Chart
|
||
|
|
|
||
|
|
|
||
|
|
# region Some metaclass wrapping magic
|
||
|
|
class _vtkWrapperMeta(type): # noqa: N801
|
||
|
|
def __init__(cls, clsname, bases, attrs) -> None:
|
||
|
|
# Restore the signature of classes inheriting from _vtkWrapper
|
||
|
|
# Based on https://stackoverflow.com/questions/49740290/call-from-metaclass-shadows-signature-of-init
|
||
|
|
sig = inspect.signature(cls.__init__) # type: ignore[misc]
|
||
|
|
params = list(sig.parameters.values())
|
||
|
|
params.insert(
|
||
|
|
len(params) - 1 if params[-1].kind == inspect.Parameter.VAR_KEYWORD else len(params),
|
||
|
|
inspect.Parameter('_wrap', inspect.Parameter.KEYWORD_ONLY, default=None),
|
||
|
|
)
|
||
|
|
cls.__signature__ = sig.replace(parameters=params[1:])
|
||
|
|
super().__init__(clsname, bases, attrs)
|
||
|
|
|
||
|
|
def __call__(cls, *args, _wrap=None, **kwargs):
|
||
|
|
obj = cls.__new__(cls, *args, **kwargs) # type: ignore[arg-type, var-annotated]
|
||
|
|
obj._wrapped = _wrap
|
||
|
|
obj.__init__(*args, **kwargs)
|
||
|
|
return obj
|
||
|
|
|
||
|
|
|
||
|
|
class _vtkWrapper(_vtk.DisableVtkSnakeCase, metaclass=_vtkWrapperMeta): # noqa: N801
|
||
|
|
def __getattribute__(self, item):
|
||
|
|
unwrapped_attrs = ['_wrapped', '__class__', '__init__']
|
||
|
|
wrapped = super().__getattribute__('_wrapped')
|
||
|
|
if item in unwrapped_attrs or wrapped is None:
|
||
|
|
return super().__getattribute__(item)
|
||
|
|
else:
|
||
|
|
if item[0].islower():
|
||
|
|
return super().__getattribute__(item)
|
||
|
|
try:
|
||
|
|
return wrapped.__getattribute__(item)
|
||
|
|
except AttributeError:
|
||
|
|
return super().__getattribute__(item)
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
if self._wrapped is None:
|
||
|
|
return super().__str__()
|
||
|
|
else:
|
||
|
|
return 'Wrapped: ' + self._wrapped.__str__()
|
||
|
|
|
||
|
|
|
||
|
|
# endregion
|
||
|
|
|
||
|
|
|
||
|
|
# region Documentation substitution
|
||
|
|
@abstract_class
|
||
|
|
class DocSubs:
|
||
|
|
"""Helper class to substitute the docstrings of the listed member functions or properties."""
|
||
|
|
|
||
|
|
# The substitutions to use for this (sub)class
|
||
|
|
_DOC_SUBS: dict[str, str] | None = None
|
||
|
|
# Internal dictionary to store registered member functions/properties and their
|
||
|
|
# (to be substituted) docs.
|
||
|
|
_DOC_STORE = {} # type: ignore[var-annotated] # noqa: RUF012
|
||
|
|
# Tag used to mark members that require docstring substitutions.
|
||
|
|
_DOC_TAG = ':DOC_SUBS:'
|
||
|
|
|
||
|
|
def __init_subclass__(cls, **kwargs) -> None:
|
||
|
|
"""Initialize subclasses."""
|
||
|
|
# First substitute all members for this class (marked in a super class)
|
||
|
|
if cls._DOC_SUBS is not None:
|
||
|
|
subs = {**cls._DOC_SUBS}
|
||
|
|
if 'cls' not in subs:
|
||
|
|
subs['cls'] = cls.__name__
|
||
|
|
for member_name, (m, d) in cls._DOC_STORE.items():
|
||
|
|
if member_name not in cls.__dict__:
|
||
|
|
# If the member is not part of the subclass' __dict__, we have to generate a
|
||
|
|
# wrapping function or property and add it to the subclass' __dict__.
|
||
|
|
# Otherwise, the docstring of the superclass would be used for the
|
||
|
|
# substitutions.
|
||
|
|
mem_sub = cls._wrap_member(m)
|
||
|
|
mem_sub.__doc__ = d
|
||
|
|
setattr(cls, member_name, mem_sub)
|
||
|
|
# Get the member function/property and safely substitute its docstring.
|
||
|
|
member = getattr(cls, member_name)
|
||
|
|
member.__doc__ = (member.__doc__ or '').format(**subs)
|
||
|
|
|
||
|
|
# Secondly, register all members of this class that require substitutions in subclasses
|
||
|
|
# Create copy of registered members so far
|
||
|
|
# TODO: B010
|
||
|
|
setattr(cls, '_DOC_STORE', {**cls._DOC_STORE}) # noqa: B010
|
||
|
|
for member_name, member in cls.__dict__.items():
|
||
|
|
if member.__doc__ and member.__doc__.startswith(cls._DOC_TAG):
|
||
|
|
# New method/property to register in this class (denoting their docstring should be
|
||
|
|
# substituted in subsequent child classes).
|
||
|
|
cls._DOC_STORE[member_name] = (member, member.__doc__[len(cls._DOC_TAG) :])
|
||
|
|
# Overwrite original docstring to prevent doctest issues
|
||
|
|
member.__doc__ = """Docstring to be specialized in subclasses."""
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def _wrap_member(member):
|
||
|
|
if callable(member):
|
||
|
|
|
||
|
|
@wraps(member)
|
||
|
|
def mem_sub(*args, **kwargs):
|
||
|
|
return member(*args, **kwargs)
|
||
|
|
|
||
|
|
elif isinstance(member, property):
|
||
|
|
mem_sub = property(member.fget, member.fset, member.fdel) # type: ignore[assignment]
|
||
|
|
else:
|
||
|
|
msg = 'Members other than methods and properties are currently not supported.'
|
||
|
|
raise NotImplementedError(msg)
|
||
|
|
return mem_sub
|
||
|
|
|
||
|
|
|
||
|
|
def doc_subs(member): # numpydoc ignore=PR01,RT01
|
||
|
|
"""Doc subs wrapper.
|
||
|
|
|
||
|
|
Only common attribute between methods and properties that we can
|
||
|
|
modify is __doc__, so use that to mark members that need doc
|
||
|
|
substitutions.
|
||
|
|
Still, only methods can be marked for doc substitution (as for
|
||
|
|
properties the docstring seems to be overwritten when specifying
|
||
|
|
setters or deleters), hence this decorator should be applied
|
||
|
|
before the property decorator.
|
||
|
|
"""
|
||
|
|
# Ensure we are operating on a method
|
||
|
|
if not callable(member): # pragma: no cover
|
||
|
|
msg = '`member` must be a callable.'
|
||
|
|
raise TypeError(msg)
|
||
|
|
|
||
|
|
# Safeguard against None docstring when using -OO
|
||
|
|
existing_doc = member.__doc__ or ''
|
||
|
|
member.__doc__ = DocSubs._DOC_TAG + existing_doc
|
||
|
|
|
||
|
|
return member
|
||
|
|
|
||
|
|
|
||
|
|
# endregion
|
||
|
|
|
||
|
|
|
||
|
|
class Pen(_vtkWrapper, _vtk.vtkPen):
|
||
|
|
"""Pythonic wrapper for a VTK Pen, used to draw lines.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
color : ColorLike, default: "k"
|
||
|
|
Color of the lines drawn using this pen. Any color parsable by
|
||
|
|
:class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
width : float, default: 1
|
||
|
|
Width of the lines drawn using this pen.
|
||
|
|
|
||
|
|
style : str, default: "-"
|
||
|
|
Style of the lines drawn using this pen. See
|
||
|
|
:ref:`Pen.LINE_STYLES <pen_line_styles>` for a list of allowed
|
||
|
|
line styles.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
.. _pen_line_styles:
|
||
|
|
|
||
|
|
LINE_STYLES : dict
|
||
|
|
Dictionary containing all allowed line styles as its keys.
|
||
|
|
|
||
|
|
.. include:: ../pen_line_styles.rst
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
LINE_STYLES: ClassVar[
|
||
|
|
dict[str, dict[str, int | str]]
|
||
|
|
] = { # descr is used in the documentation, set to None to hide it from the docs.
|
||
|
|
'': {'id': _vtk.vtkPen.NO_PEN, 'descr': 'Hidden'},
|
||
|
|
'-': {'id': _vtk.vtkPen.SOLID_LINE, 'descr': 'Solid'},
|
||
|
|
'--': {'id': _vtk.vtkPen.DASH_LINE, 'descr': 'Dashed'},
|
||
|
|
':': {'id': _vtk.vtkPen.DOT_LINE, 'descr': 'Dotted'},
|
||
|
|
'-.': {'id': _vtk.vtkPen.DASH_DOT_LINE, 'descr': 'Dash-dot'},
|
||
|
|
'-..': {'id': _vtk.vtkPen.DASH_DOT_DOT_LINE, 'descr': 'Dash-dot-dot'},
|
||
|
|
}
|
||
|
|
|
||
|
|
def __init__(self, color='k', width=1, style='-') -> None:
|
||
|
|
"""Initialize a new Pen instance."""
|
||
|
|
super().__init__()
|
||
|
|
self.color = color
|
||
|
|
self.width = width
|
||
|
|
self.style = style
|
||
|
|
|
||
|
|
@property
|
||
|
|
def color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the pen's color.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the pen's color to red.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> plot.pen.color = 'r'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._color
|
||
|
|
|
||
|
|
@color.setter
|
||
|
|
def color(self, val) -> None:
|
||
|
|
self._color = Color(val, default_color='black')
|
||
|
|
self.SetColor(*self._color.int_rgba)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def width(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the pen's width.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the pen's width to 10
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> plot.pen.width = 10
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetWidth()
|
||
|
|
|
||
|
|
@width.setter
|
||
|
|
def width(self, val) -> None:
|
||
|
|
self.SetWidth(float(val))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def style(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the pen's line style.
|
||
|
|
|
||
|
|
See :ref:`Pen.LINE_STYLES <pen_line_styles>` for a list of allowed line styles.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> plot.pen.style = '-.'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._line_style
|
||
|
|
|
||
|
|
@style.setter
|
||
|
|
def style(self, val):
|
||
|
|
if val is None:
|
||
|
|
val = ''
|
||
|
|
try:
|
||
|
|
self.SetLineType(self.LINE_STYLES[val]['id']) # type: ignore[arg-type]
|
||
|
|
self._line_style = val
|
||
|
|
except KeyError:
|
||
|
|
formatted_styles = '", "'.join(self.LINE_STYLES.keys())
|
||
|
|
msg = f'Invalid line style. Allowed line styles: "{formatted_styles}"'
|
||
|
|
raise ValueError(msg)
|
||
|
|
|
||
|
|
|
||
|
|
class Brush(_vtkWrapper, _vtk.vtkBrush):
|
||
|
|
"""Pythonic wrapper for a VTK Brush, used to fill shapes.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
color : ColorLike, default: "k"
|
||
|
|
Fill color of the shapes drawn using this brush. Any color
|
||
|
|
parsable by :class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
texture : pyvista.Texture, optional
|
||
|
|
Texture used to fill shapes drawn using this brush. Any object
|
||
|
|
convertible to a :class:`pyvista.Texture` is allowed. Defaults to
|
||
|
|
``None``.
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, color='k', texture=None) -> None:
|
||
|
|
"""Initialize a new Pen instance."""
|
||
|
|
super().__init__()
|
||
|
|
self.color = color
|
||
|
|
self.texture = texture
|
||
|
|
self._interpolate = True # vtkBrush textureProperties defaults to LINEAR & STRETCH
|
||
|
|
self._repeat = False
|
||
|
|
|
||
|
|
@property
|
||
|
|
def color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the brush's color.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the brush's color to red.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [0, 0, 1], [1, 3, 2])
|
||
|
|
>>> plot.brush.color = 'r'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._color
|
||
|
|
|
||
|
|
@color.setter
|
||
|
|
def color(self, val) -> None:
|
||
|
|
self._color = Color(val, default_color='black')
|
||
|
|
self.SetColor(*self._color.int_rgba)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def texture(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the brush's texture.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the brush's texture to the sample puppy texture.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> from pyvista import examples
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [0, 0, 1], [1, 3, 2])
|
||
|
|
>>> plot.brush.texture = examples.download_puppy_texture()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._texture
|
||
|
|
|
||
|
|
@texture.setter
|
||
|
|
def texture(self, val) -> None:
|
||
|
|
if val is None:
|
||
|
|
self._texture = None
|
||
|
|
self.SetTexture(None)
|
||
|
|
else:
|
||
|
|
self._texture = pyvista.Texture(val) # type: ignore[abstract]
|
||
|
|
self.SetTexture(self._texture.to_image())
|
||
|
|
|
||
|
|
@property
|
||
|
|
def texture_interpolate(self): # numpydoc ignore=RT01
|
||
|
|
"""Set texture interpolation mode.
|
||
|
|
|
||
|
|
There are two modes:
|
||
|
|
|
||
|
|
* ``False`` - NEAREST
|
||
|
|
* ``True`` - LINEAR
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set up a brush with a texture.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> from pyvista import examples
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [0, 0, 1], [1, 3, 2])
|
||
|
|
>>> plot.brush.texture = examples.download_puppy_texture()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Disable linear interpolation.
|
||
|
|
|
||
|
|
>>> plot.brush.texture_interpolate = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._interpolate
|
||
|
|
|
||
|
|
@texture_interpolate.setter
|
||
|
|
def texture_interpolate(self, val) -> None:
|
||
|
|
self._interpolate = bool(val)
|
||
|
|
self._update_textureprops()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def texture_repeat(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the texture repeat mode.
|
||
|
|
|
||
|
|
There are two modes:
|
||
|
|
|
||
|
|
* ``False`` - STRETCH
|
||
|
|
* ``True`` - REPEAT
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set up a brush with a texture.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> from pyvista import examples
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [0, 0, 1], [1, 3, 2])
|
||
|
|
>>> plot.brush.texture = examples.download_puppy_texture()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Enable texture repeat.
|
||
|
|
|
||
|
|
>>> plot.brush.texture_repeat = True
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._repeat
|
||
|
|
|
||
|
|
@texture_repeat.setter
|
||
|
|
def texture_repeat(self, val) -> None:
|
||
|
|
self._repeat = bool(val)
|
||
|
|
self._update_textureprops()
|
||
|
|
|
||
|
|
def _update_textureprops(self) -> None:
|
||
|
|
# Interpolation: NEAREST = 0x01, LINEAR = 0x02
|
||
|
|
# Stretch/repeat: STRETCH = 0x04, REPEAT = 0x08
|
||
|
|
self.SetTextureProperties(1 + int(self._interpolate) + 4 * (1 + int(self._repeat)))
|
||
|
|
|
||
|
|
|
||
|
|
class Axis(_vtkWrapper, _vtk.vtkAxis):
|
||
|
|
"""Pythonic interface for a VTK Axis, used by 2D charts.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
label : str, default: ""
|
||
|
|
Axis label.
|
||
|
|
|
||
|
|
range : sequence[float], optional
|
||
|
|
Axis range, denoting the minimum and maximum values
|
||
|
|
displayed on this axis. Setting this to any valid value
|
||
|
|
other than ``None`` will change this axis behavior to
|
||
|
|
``'fixed'``. Setting it to ``None`` will change the axis
|
||
|
|
behavior to ``'auto'``.
|
||
|
|
|
||
|
|
grid : bool, default: True
|
||
|
|
Flag to toggle grid lines visibility for this axis.
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
BEHAVIORS: ClassVar[dict[str, int]] = {'auto': _vtk.vtkAxis.AUTO, 'fixed': _vtk.vtkAxis.FIXED}
|
||
|
|
|
||
|
|
@_deprecate_positional_args
|
||
|
|
def __init__(self, label='', range=None, grid: bool = True) -> None: # noqa: A002, FBT001, FBT002
|
||
|
|
"""Initialize a new Axis instance."""
|
||
|
|
super().__init__()
|
||
|
|
self._tick_locs = _vtk.vtkDoubleArray()
|
||
|
|
self._tick_labels = _vtk.vtkStringArray()
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
# SetPen and SetGridPen methods are not available for older VTK versions,
|
||
|
|
# so fallback to using wrapper objects.
|
||
|
|
self._pen = Pen(color=(0, 0, 0), _wrap=self.GetPen()) # type: ignore[call-arg]
|
||
|
|
self._grid_pen = Pen(color=(0.95, 0.95, 0.95), _wrap=self.GetGridPen()) # type: ignore[call-arg]
|
||
|
|
else:
|
||
|
|
self._pen = Pen(color=(0, 0, 0))
|
||
|
|
self._grid_pen = Pen(color=(0.95, 0.95, 0.95))
|
||
|
|
self.SetPen(self._pen)
|
||
|
|
self.SetGridPen(self._grid_pen)
|
||
|
|
self.label = label
|
||
|
|
self._behavior = None # Will be set by specifying the range below
|
||
|
|
self.range = range
|
||
|
|
self.grid = grid
|
||
|
|
|
||
|
|
@property
|
||
|
|
def pen(self) -> Pen: # numpydoc ignore=RT01
|
||
|
|
"""Pen used to draw the axis."""
|
||
|
|
return self._pen
|
||
|
|
|
||
|
|
@property
|
||
|
|
def grid_pen(self) -> Pen: # numpydoc ignore=RT01
|
||
|
|
"""Pen used to draw the grid lines."""
|
||
|
|
return self._grid_pen
|
||
|
|
|
||
|
|
@property
|
||
|
|
def label(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the axis label.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the axis label to ``"Axis Label"``.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.label = 'Axis Label'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTitle()
|
||
|
|
|
||
|
|
@label.setter
|
||
|
|
def label(self, val) -> None:
|
||
|
|
self.SetTitle(val)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def label_visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the axis label's visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Hide the x-axis label of a 2D chart.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.label_visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTitleVisible()
|
||
|
|
|
||
|
|
@label_visible.setter
|
||
|
|
def label_visible(self, val) -> None:
|
||
|
|
self.SetTitleVisible(bool(val))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def label_size(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the size of the axis label font.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the x-axis label font size of a 2D chart to 20.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.label_size = 20
|
||
|
|
>>> chart.x_axis.label_size
|
||
|
|
20
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTitleProperties().GetFontSize()
|
||
|
|
|
||
|
|
@label_size.setter
|
||
|
|
def label_size(self, size) -> None:
|
||
|
|
self.GetTitleProperties().SetFontSize(size)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def range(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the axis range.
|
||
|
|
|
||
|
|
This will automatically set the axis behavior to ``"fixed"``
|
||
|
|
when a valid range is given. Setting the range to ``None``
|
||
|
|
will set the axis behavior to ``"auto"``.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Manually specify the x-axis range of a 2D chart.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.range = [0, 5]
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Revert to automatic axis scaling.
|
||
|
|
|
||
|
|
>>> chart.x_axis.range = None
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
r = [0.0, 0.0]
|
||
|
|
self.GetRange(r)
|
||
|
|
return r
|
||
|
|
|
||
|
|
@range.setter
|
||
|
|
def range(self, val) -> None:
|
||
|
|
if val is None:
|
||
|
|
self.behavior = 'auto'
|
||
|
|
else:
|
||
|
|
self.behavior = 'fixed'
|
||
|
|
self.SetRange(*val)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def behavior(self): # numpydoc ignore=RT01
|
||
|
|
"""Set the axis' scaling behavior.
|
||
|
|
|
||
|
|
Allowed behaviors are ``'auto'`` to automatically rescale the
|
||
|
|
axis to fit all visible datapoints in the plot, or ``'fixed'``
|
||
|
|
to use the user defined range.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Manually specify the x-axis range of a 2D chart.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.range = [0, 5]
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Revert to automatic axis scaling.
|
||
|
|
|
||
|
|
>>> chart.x_axis.behavior = 'auto'
|
||
|
|
>>> chart.show()
|
||
|
|
>>> chart.x_axis.range
|
||
|
|
[0.0, 2.0]
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._behavior
|
||
|
|
|
||
|
|
@behavior.setter
|
||
|
|
def behavior(self, val):
|
||
|
|
try:
|
||
|
|
self.SetBehavior(self.BEHAVIORS[val])
|
||
|
|
self._behavior = val
|
||
|
|
except KeyError:
|
||
|
|
formatted_behaviors = '", "'.join(self.BEHAVIORS.keys())
|
||
|
|
msg = f'Invalid behavior. Allowed behaviors: "{formatted_behaviors}"'
|
||
|
|
raise ValueError(msg)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def margin(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the axis margin.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> chart.background_color = 'c'
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Manually specify a larger (bottom) margin for the x-axis and a
|
||
|
|
larger (left) margin for the y-axis.
|
||
|
|
|
||
|
|
>>> chart.x_axis.margin = 50
|
||
|
|
>>> chart.y_axis.margin = 50
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetMargins()[0]
|
||
|
|
|
||
|
|
@margin.setter
|
||
|
|
def margin(self, val) -> None:
|
||
|
|
# Second margin doesn't seem to have any effect? So we only expose the first entry as
|
||
|
|
# 'the margin'.
|
||
|
|
m = self.GetMargins()
|
||
|
|
self.SetMargins(val, m[1])
|
||
|
|
|
||
|
|
@property
|
||
|
|
def log_scale(self): # numpydoc ignore=RT01
|
||
|
|
"""Flag denoting whether a log scale is used for this axis.
|
||
|
|
|
||
|
|
Note that setting this property to ``True`` will not guarantee
|
||
|
|
that the log scale will be enabled. Verify whether activating
|
||
|
|
the log scale succeeded by rereading this property.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2, 3, 4], [1e0, 1e1, 1e2, 1e3, 1e4])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Try to enable the log scale on the y-axis.
|
||
|
|
|
||
|
|
>>> chart.y_axis.log_scale = True
|
||
|
|
>>> chart.show()
|
||
|
|
>>> chart.y_axis.log_scale
|
||
|
|
True
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetLogScaleActive()
|
||
|
|
|
||
|
|
@log_scale.setter
|
||
|
|
def log_scale(self, val) -> None:
|
||
|
|
# False: log_scale will be disabled
|
||
|
|
# True: axis will attempt to activate log_scale if possible
|
||
|
|
self.SetLogScale(bool(val))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def grid(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the axis' grid line visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with grid lines disabled for the x-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.grid = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetGridVisible()
|
||
|
|
|
||
|
|
@grid.setter
|
||
|
|
def grid(self, val) -> None:
|
||
|
|
self.SetGridVisible(bool(val))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the axis' visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with no visible y-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.y_axis.visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetAxisVisible()
|
||
|
|
|
||
|
|
@visible.setter
|
||
|
|
def visible(self, val) -> None:
|
||
|
|
self.SetAxisVisible(bool(val))
|
||
|
|
|
||
|
|
def toggle(self) -> None:
|
||
|
|
"""Toggle the axis' visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Toggle the visibility of the y-axis.
|
||
|
|
|
||
|
|
>>> chart.y_axis.toggle()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.visible = not self.visible
|
||
|
|
|
||
|
|
# --- Ticks ---
|
||
|
|
@property
|
||
|
|
def tick_count(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the number of ticks drawn on this axis.
|
||
|
|
|
||
|
|
Setting this property to a negative value or ``None`` will
|
||
|
|
automatically determine the appropriate amount of ticks to
|
||
|
|
draw.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with a reduced number of ticks on the x-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.tick_count = 5
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Revert back to automatic tick behavior.
|
||
|
|
|
||
|
|
>>> chart.x_axis.tick_count = None
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetNumberOfTicks()
|
||
|
|
|
||
|
|
@tick_count.setter
|
||
|
|
def tick_count(self, val) -> None:
|
||
|
|
if val is None or val < 0:
|
||
|
|
val = -1
|
||
|
|
self.SetNumberOfTicks(int(val))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def tick_locations(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the tick locations for this axis.
|
||
|
|
|
||
|
|
Setting this to ``None`` will revert back to the default,
|
||
|
|
automatically determined, tick locations.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with custom tick locations and labels on the y-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.y_axis.tick_locations = (0.2, 0.4, 0.6, 1, 1.5, 2, 3)
|
||
|
|
>>> chart.y_axis.tick_labels = [
|
||
|
|
... 'Very small',
|
||
|
|
... 'Small',
|
||
|
|
... 'Still small',
|
||
|
|
... 'Small?',
|
||
|
|
... 'Not large',
|
||
|
|
... 'Large?',
|
||
|
|
... 'Very large',
|
||
|
|
... ]
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Revert back to automatic tick placement.
|
||
|
|
|
||
|
|
>>> chart.y_axis.tick_locations = None
|
||
|
|
>>> chart.y_axis.tick_labels = None
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
positions = self.GetTickPositions()
|
||
|
|
return tuple(positions.GetValue(i) for i in range(positions.GetNumberOfValues()))
|
||
|
|
|
||
|
|
@tick_locations.setter
|
||
|
|
def tick_locations(self, val) -> None:
|
||
|
|
self._tick_locs.Reset()
|
||
|
|
if val is not None:
|
||
|
|
for loc in val:
|
||
|
|
self._tick_locs.InsertNextValue(loc)
|
||
|
|
self._update_ticks()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def tick_labels(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the tick labels for this axis.
|
||
|
|
|
||
|
|
You can specify a sequence, to provide a unique label to every
|
||
|
|
tick position; a string, to describe the label format to use
|
||
|
|
for each label; or ``None``, which will revert back to the
|
||
|
|
default tick labels. A label format is a string consisting of
|
||
|
|
an integer part, denoting the precision to use, and a final
|
||
|
|
character, denoting the notation to use.
|
||
|
|
|
||
|
|
Allowed notations:
|
||
|
|
|
||
|
|
* ``"f"`` for fixed notation
|
||
|
|
* ``"e"`` for scientific notation.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with custom tick locations and labels on the y-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.y_axis.tick_locations = (0.2, 0.4, 0.6, 1, 1.5, 2, 3)
|
||
|
|
>>> chart.y_axis.tick_labels = [
|
||
|
|
... 'Very small',
|
||
|
|
... 'Small',
|
||
|
|
... 'Still small',
|
||
|
|
... 'Small?',
|
||
|
|
... 'Not large',
|
||
|
|
... 'Large?',
|
||
|
|
... 'Very large',
|
||
|
|
... ]
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Revert back to automatic tick placement.
|
||
|
|
|
||
|
|
>>> chart.y_axis.tick_locations = None
|
||
|
|
>>> chart.y_axis.tick_labels = None
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Specify a custom label format to use (fixed notation with precision 2).
|
||
|
|
|
||
|
|
>>> chart.y_axis.tick_labels = '2f'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
labels = self.GetTickLabels()
|
||
|
|
return tuple(labels.GetValue(i) for i in range(labels.GetNumberOfValues()))
|
||
|
|
|
||
|
|
@tick_labels.setter
|
||
|
|
def tick_labels(self, val) -> None:
|
||
|
|
self._tick_labels.Reset()
|
||
|
|
self.SetNotation(_vtk.vtkAxis.STANDARD_NOTATION)
|
||
|
|
if isinstance(val, str):
|
||
|
|
precision = int(val[:-1])
|
||
|
|
notation = val[-1].lower()
|
||
|
|
if notation == 'f':
|
||
|
|
self.SetNotation(_vtk.vtkAxis.FIXED_NOTATION)
|
||
|
|
self.SetPrecision(precision)
|
||
|
|
elif notation == 'e':
|
||
|
|
self.SetNotation(_vtk.vtkAxis.SCIENTIFIC_NOTATION)
|
||
|
|
self.SetPrecision(precision)
|
||
|
|
elif isinstance(val, Sequence):
|
||
|
|
for label in val:
|
||
|
|
self._tick_labels.InsertNextValue(label)
|
||
|
|
self._update_ticks()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def tick_label_size(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the size of the axis tick label font.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the x-axis tick label font size of a 2D chart to 20.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.tick_label_size = 20
|
||
|
|
>>> chart.x_axis.tick_label_size
|
||
|
|
20
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetLabelProperties().GetFontSize()
|
||
|
|
|
||
|
|
@tick_label_size.setter
|
||
|
|
def tick_label_size(self, size) -> None:
|
||
|
|
self.GetLabelProperties().SetFontSize(size)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def tick_size(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the size of this axis' ticks.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with an x-axis with an increased tick size
|
||
|
|
and adjusted offset for the tick labels.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.tick_size += 10
|
||
|
|
>>> chart.x_axis.tick_labels_offset += 12
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTickLength()
|
||
|
|
|
||
|
|
@tick_size.setter
|
||
|
|
def tick_size(self, val) -> None:
|
||
|
|
self.SetTickLength(val)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def tick_labels_offset(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the offset of the tick labels for this axis.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with an x-axis with an increased tick size
|
||
|
|
and adjusted offset for the tick labels.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.tick_size += 10
|
||
|
|
>>> chart.x_axis.tick_labels_offset += 12
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetLabelOffset()
|
||
|
|
|
||
|
|
@tick_labels_offset.setter
|
||
|
|
def tick_labels_offset(self, val) -> None:
|
||
|
|
self.SetLabelOffset(float(val))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def tick_labels_visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the tick label visibility for this axis.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with hidden tick labels on the y-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.y_axis.tick_labels_visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetLabelsVisible()
|
||
|
|
|
||
|
|
@tick_labels_visible.setter
|
||
|
|
def tick_labels_visible(self, val) -> None:
|
||
|
|
self.SetLabelsVisible(bool(val))
|
||
|
|
self.SetRangeLabelsVisible(bool(val))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def ticks_visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the tick visibility for this axis.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with hidden ticks on the y-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.y_axis.ticks_visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTicksVisible()
|
||
|
|
|
||
|
|
@ticks_visible.setter
|
||
|
|
def ticks_visible(self, val) -> None:
|
||
|
|
self.SetTicksVisible(bool(val))
|
||
|
|
|
||
|
|
def _update_ticks(self) -> None:
|
||
|
|
locs = None if self._tick_locs.GetNumberOfValues() == 0 else self._tick_locs
|
||
|
|
labels = None if self._tick_labels.GetNumberOfValues() == 0 else self._tick_labels
|
||
|
|
self.SetCustomTickPositions(locs, labels)
|
||
|
|
|
||
|
|
|
||
|
|
@abstract_class
|
||
|
|
class _CustomContextItem(_vtk.vtkPythonItem):
|
||
|
|
class ItemWrapper:
|
||
|
|
def Initialize(self, item) -> bool: # noqa: ARG002, N802
|
||
|
|
# item is the _CustomContextItem subclass instance
|
||
|
|
return True
|
||
|
|
|
||
|
|
def Paint(self, item, painter): # noqa: N802
|
||
|
|
# item is the _CustomContextItem subclass instance
|
||
|
|
return item.paint(painter)
|
||
|
|
|
||
|
|
def __init__(self) -> None:
|
||
|
|
super().__init__()
|
||
|
|
# This will also call ItemWrapper.Initialize
|
||
|
|
self.SetPythonObject(_CustomContextItem.ItemWrapper())
|
||
|
|
|
||
|
|
def paint(self, _) -> bool:
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
class _ChartBackground(_vtk.DisableVtkSnakeCase, _CustomContextItem):
|
||
|
|
"""Utility class for chart backgrounds."""
|
||
|
|
|
||
|
|
def __init__(self, chart) -> None:
|
||
|
|
super().__init__()
|
||
|
|
# Note: This SHOULD be a weakref proxy, as otherwise the garbage collector will not clean
|
||
|
|
# up unused charts (because of the cyclic references between charts and their background).
|
||
|
|
self._chart = weakref.proxy(chart) # Weakref proxy to the chart to draw the background for
|
||
|
|
# Default background is translucent with black border line
|
||
|
|
self.BorderPen = Pen(color=(0, 0, 0))
|
||
|
|
self.BackgroundBrush = Brush(color=(0, 0, 0, 0))
|
||
|
|
# Default active background is slightly more opaque with yellow border line
|
||
|
|
self.ActiveBorderPen = Pen(color=(0.8, 0.8, 0.2))
|
||
|
|
self.ActiveBackgroundBrush = Brush(color=(1.0, 1.0, 1.0, 0.4))
|
||
|
|
|
||
|
|
def paint(self, painter) -> bool:
|
||
|
|
if self._chart.visible:
|
||
|
|
painter.ApplyPen(self.ActiveBorderPen if self._chart._interactive else self.BorderPen)
|
||
|
|
painter.ApplyBrush(
|
||
|
|
self.ActiveBackgroundBrush if self._chart._interactive else self.BackgroundBrush,
|
||
|
|
)
|
||
|
|
l, b, w, h = self._chart._geometry
|
||
|
|
painter.DrawRect(l, b, w, h)
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
# Following 'patch' is necessary for earlier VTK versions. Otherwise Pie plots
|
||
|
|
# will use the same opacity as the chart's background when their legend is hidden.
|
||
|
|
# As the default background is transparent, this will cause Pie charts to
|
||
|
|
# completely disappear.
|
||
|
|
painter.GetBrush().SetOpacity(255)
|
||
|
|
painter.GetBrush().SetTexture(None)
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
@abstract_class
|
||
|
|
class _Chart(DocSubs):
|
||
|
|
"""Common interface for vtkChart, vtkChartBox, vtkChartPie, and ChartMPL instances."""
|
||
|
|
|
||
|
|
# Subclasses should specify following substitutions: 'chart_name', 'chart_args', 'chart_init'
|
||
|
|
# and 'chart_set_labels'.
|
||
|
|
_DOC_SUBS: dict[str, str] | None = None
|
||
|
|
|
||
|
|
def __init__(self, size=(1, 1), loc=(0, 0)) -> None:
|
||
|
|
super().__init__()
|
||
|
|
self._background = _ChartBackground(self)
|
||
|
|
self._x_axis = Axis()
|
||
|
|
self._y_axis = Axis()
|
||
|
|
if size is not None:
|
||
|
|
self.size = size
|
||
|
|
if loc is not None:
|
||
|
|
self.loc = loc
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _scene(self):
|
||
|
|
"""Get a reference to the :vtk:`vtkScene` in which this chart is drawn."""
|
||
|
|
return self.GetScene() # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _renderer(self):
|
||
|
|
"""Get a reference to the :vtk:`vtkRenderer` in which this chart is drawn."""
|
||
|
|
return self._scene.GetRenderer() if self._scene is not None else None
|
||
|
|
|
||
|
|
def _render_event(self, *_, plotter_render: bool = False, **__) -> None:
|
||
|
|
"""Update the chart right before it will be rendered."""
|
||
|
|
# Only resize on real VTK render events (plotter.render calls will afterwards invoke a
|
||
|
|
# proper render event)
|
||
|
|
if not plotter_render:
|
||
|
|
self._resize()
|
||
|
|
|
||
|
|
def _resize(self):
|
||
|
|
"""Resize this chart.
|
||
|
|
|
||
|
|
Resize this chart such that it always occupies the specified
|
||
|
|
geometry (matching the specified location and size).
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
bool
|
||
|
|
``True`` if the chart was resized, ``False`` otherwise.
|
||
|
|
|
||
|
|
"""
|
||
|
|
# edge race case
|
||
|
|
if self._renderer is None: # pragma: no cover
|
||
|
|
return None
|
||
|
|
|
||
|
|
r_w, r_h = self._renderer.GetSize()
|
||
|
|
# Alternatively: self.scene.GetViewWidth(), self.scene.GetViewHeight()
|
||
|
|
_, _, c_w, c_h = (int(g) for g in self._geometry)
|
||
|
|
# Target size is calculated from specified normalized width and height and the renderer's
|
||
|
|
# current size
|
||
|
|
t_w = int(self._size[0] * r_w)
|
||
|
|
t_h = int(self._size[1] * r_h)
|
||
|
|
resize = c_w != t_w or c_h != t_h
|
||
|
|
if resize:
|
||
|
|
# Mismatch between current size and target size, so resize chart:
|
||
|
|
self._geometry = (int(self._loc[0] * r_w), int(self._loc[1] * r_h), t_w, t_h)
|
||
|
|
return resize
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _geometry(self):
|
||
|
|
"""Chart geometry (x, y position of bottom left corner and width and height in pixels)."""
|
||
|
|
return tuple(self.GetSize()) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@_geometry.setter
|
||
|
|
def _geometry(self, val) -> None:
|
||
|
|
"""Set the chart geometry."""
|
||
|
|
self.SetSize(_vtk.vtkRectf(*val)) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _interactive(self):
|
||
|
|
"""Return or set the chart's interactivity.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
Users should not set this property directly, but use the
|
||
|
|
:func:`Renderer.set_chart_interaction` method instead.
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetInteractive() # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@_interactive.setter
|
||
|
|
def _interactive(self, val) -> None:
|
||
|
|
self.SetInteractive(val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
def _is_within(self, pos):
|
||
|
|
"""Check whether the specified position (in pixels) lies within this chart's geometry."""
|
||
|
|
l, b, w, h = self._geometry
|
||
|
|
return l <= pos[0] <= l + w and b <= pos[1] <= b + h
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def size(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart size in normalized coordinates.
|
||
|
|
|
||
|
|
A size of ``(1, 1)`` occupies the whole renderer.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a half-sized {chart_name} centered in the middle of the
|
||
|
|
renderer.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.size = (0.5, 0.5)
|
||
|
|
>>> chart.loc = (0.25, 0.25)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._size
|
||
|
|
|
||
|
|
@size.setter
|
||
|
|
def size(self, val):
|
||
|
|
if not (len(val) == 2 and 0 <= val[0] <= 1 and 0 <= val[1] <= 1):
|
||
|
|
msg = f'Invalid size {val}.'
|
||
|
|
raise ValueError(msg)
|
||
|
|
self._size = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def loc(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart position in normalized coordinates.
|
||
|
|
|
||
|
|
This denotes the location of the chart's bottom left corner.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a half-sized {chart_name} centered in the middle of the
|
||
|
|
renderer.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.size = (0.5, 0.5)
|
||
|
|
>>> chart.loc = (0.25, 0.25)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._loc
|
||
|
|
|
||
|
|
@loc.setter
|
||
|
|
def loc(self, val):
|
||
|
|
if not (len(val) == 2 and 0 <= val[0] <= 1 and 0 <= val[1] <= 1):
|
||
|
|
msg = f'Invalid loc {val}.'
|
||
|
|
raise ValueError(msg)
|
||
|
|
self._loc = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def border_color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's border color.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with a thick, dashed red border.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.border_color = 'r'
|
||
|
|
>>> chart.border_width = 5
|
||
|
|
>>> chart.border_style = '--'
|
||
|
|
>>> chart.show(interactive=False)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._background.BorderPen.color
|
||
|
|
|
||
|
|
@border_color.setter
|
||
|
|
def border_color(self, val) -> None:
|
||
|
|
self._background.BorderPen.color = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def border_width(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's border width.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with a thick, dashed red border.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.border_color = 'r'
|
||
|
|
>>> chart.border_width = 5
|
||
|
|
>>> chart.border_style = '--'
|
||
|
|
>>> chart.show(interactive=False)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._background.BorderPen.width
|
||
|
|
|
||
|
|
@border_width.setter
|
||
|
|
def border_width(self, val) -> None:
|
||
|
|
self._background.BorderPen.width = val
|
||
|
|
self._background.ActiveBorderPen.width = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def border_style(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's border style.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with a thick, dashed red border.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.border_color = 'r'
|
||
|
|
>>> chart.border_width = 5
|
||
|
|
>>> chart.border_style = '--'
|
||
|
|
>>> chart.show(interactive=False)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._background.BorderPen.style
|
||
|
|
|
||
|
|
@border_style.setter
|
||
|
|
def border_style(self, val) -> None:
|
||
|
|
self._background.BorderPen.style = val
|
||
|
|
self._background.ActiveBorderPen.style = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def active_border_color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's border color in interactive mode.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with a thick, dashed red border.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.border_color = 'r'
|
||
|
|
>>> chart.border_width = 5
|
||
|
|
>>> chart.border_style = '--'
|
||
|
|
>>> chart.show(interactive=False)
|
||
|
|
|
||
|
|
Set the active border color to yellow and activate the chart.
|
||
|
|
|
||
|
|
>>> chart.active_border_color = 'y'
|
||
|
|
>>> chart.show(interactive=True)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._background.ActiveBorderPen.color
|
||
|
|
|
||
|
|
@active_border_color.setter
|
||
|
|
def active_border_color(self, val) -> None:
|
||
|
|
self._background.ActiveBorderPen.color = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def background_color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's background color.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with a green background.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.background_color = (0.5, 0.9, 0.5)
|
||
|
|
>>> chart.show(interactive=False)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._background.BackgroundBrush.color
|
||
|
|
|
||
|
|
@background_color.setter
|
||
|
|
def background_color(self, val) -> None:
|
||
|
|
self._background.BackgroundBrush.color = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def background_texture(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's background texture.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with an emoji as its background.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> from pyvista import examples
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.background_texture = examples.download_emoji_texture()
|
||
|
|
>>> chart.show(interactive=False)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._background.BackgroundBrush.texture
|
||
|
|
|
||
|
|
@background_texture.setter
|
||
|
|
def background_texture(self, val) -> None:
|
||
|
|
self._background.BackgroundBrush.texture = val
|
||
|
|
self._background.ActiveBackgroundBrush.texture = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def active_background_color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's background color in interactive mode.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with a green background.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.background_color = (0.5, 0.9, 0.5)
|
||
|
|
>>> chart.show(interactive=False)
|
||
|
|
|
||
|
|
Set the active background color to blue and activate the chart.
|
||
|
|
|
||
|
|
>>> chart.active_background_color = 'b'
|
||
|
|
>>> chart.show(interactive=True)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._background.ActiveBackgroundBrush.color
|
||
|
|
|
||
|
|
@active_background_color.setter
|
||
|
|
def active_background_color(self, val) -> None:
|
||
|
|
self._background.ActiveBackgroundBrush.color = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name}.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Hide it.
|
||
|
|
|
||
|
|
>>> chart.visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetVisible() # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@visible.setter
|
||
|
|
def visible(self, val) -> None:
|
||
|
|
self.SetVisible(val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@doc_subs
|
||
|
|
def toggle(self) -> None:
|
||
|
|
"""Toggle the chart's visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name}.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Hide it.
|
||
|
|
|
||
|
|
>>> chart.toggle()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.visible = not self.visible
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def title(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's title.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with title 'My Chart'.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.title = 'My Chart'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTitle() # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@title.setter
|
||
|
|
def title(self, val) -> None:
|
||
|
|
self.SetTitle(val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def legend_visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the visibility of the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {chart_name} with custom labels.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> {chart_set_labels}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Hide the legend.
|
||
|
|
|
||
|
|
>>> chart.legend_visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetShowLegend() # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@legend_visible.setter
|
||
|
|
def legend_visible(self, val) -> None:
|
||
|
|
self.SetShowLegend(val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@_deprecate_positional_args
|
||
|
|
@doc_subs
|
||
|
|
def show( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
interactive: bool = True, # noqa: FBT001, FBT002
|
||
|
|
off_screen=None,
|
||
|
|
full_screen=None,
|
||
|
|
screenshot=None,
|
||
|
|
window_size=None,
|
||
|
|
notebook=None,
|
||
|
|
background='w',
|
||
|
|
dev_kwargs=None,
|
||
|
|
):
|
||
|
|
"""Show this chart in a self contained plotter.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
interactive : bool, default: True
|
||
|
|
Enable interaction with the chart. Interaction is not enabled
|
||
|
|
when plotting off screen.
|
||
|
|
|
||
|
|
off_screen : bool, optional
|
||
|
|
Plots off screen when ``True``. Helpful for saving screenshots
|
||
|
|
without a window popping up. Defaults to active theme setting.
|
||
|
|
|
||
|
|
full_screen : bool, optional
|
||
|
|
Opens window in full screen. When enabled, ignores
|
||
|
|
``window_size``. Defaults to active theme setting.
|
||
|
|
|
||
|
|
screenshot : str | bool, default: False
|
||
|
|
Saves screenshot to file when enabled. See:
|
||
|
|
:func:`Plotter.screenshot() <pyvista.Plotter.screenshot>`.
|
||
|
|
|
||
|
|
When ``True``, takes screenshot and returns ``numpy`` array of
|
||
|
|
image.
|
||
|
|
|
||
|
|
window_size : list, optional
|
||
|
|
Window size in pixels. Defaults to active theme setting.
|
||
|
|
|
||
|
|
notebook : bool, optional
|
||
|
|
When ``True``, the resulting plot is placed inline a
|
||
|
|
jupyter notebook. Assumes a jupyter console is active.
|
||
|
|
|
||
|
|
background : ColorLike, default: "w"
|
||
|
|
Use to make the entire mesh have a single solid color.
|
||
|
|
Either a string, RGB list, or hex color string. For example:
|
||
|
|
``color='white'``, ``color='w'``, ``color=[1.0, 1.0, 1.0]``, or
|
||
|
|
``color='#FFFFFF'``.
|
||
|
|
|
||
|
|
dev_kwargs : dict, optional
|
||
|
|
Optional developer keyword arguments.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
np.ndarray
|
||
|
|
Numpy array of the last image when ``screenshot=True``
|
||
|
|
is set. Optionally contains alpha values. Sized:
|
||
|
|
|
||
|
|
* [Window height x Window width x 3] if the theme sets
|
||
|
|
``transparent_background=False``.
|
||
|
|
* [Window height x Window width x 4] if the theme sets
|
||
|
|
``transparent_background=True``.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a simple {chart_name} and show it.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.{cls}({chart_args}){chart_init}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if dev_kwargs is None:
|
||
|
|
dev_kwargs = {}
|
||
|
|
if off_screen is None:
|
||
|
|
off_screen = pyvista.OFF_SCREEN
|
||
|
|
pl = pyvista.Plotter(window_size=window_size, notebook=notebook, off_screen=off_screen)
|
||
|
|
pl.background_color = background
|
||
|
|
pl.add_chart(self)
|
||
|
|
if interactive and (not off_screen or pyvista.BUILDING_GALLERY): # pragma: no cover
|
||
|
|
pl.set_chart_interaction(self)
|
||
|
|
return pl.show(
|
||
|
|
screenshot=screenshot,
|
||
|
|
full_screen=full_screen,
|
||
|
|
**dev_kwargs,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
# Subclasses of `_Plot` also inherit from vtk classes, so we disable the vtk snake_case API here
|
||
|
|
@abstract_class
|
||
|
|
class _Plot(DocSubs):
|
||
|
|
"""Common pythonic interface for :vtk:`vtkPlot` and :vtk:`vtkPlot3D` instances."""
|
||
|
|
|
||
|
|
# Subclasses should specify following substitutions: 'plot_name', 'chart_init' and 'plot_init'.
|
||
|
|
_DOC_SUBS: dict[str, str] | None = None
|
||
|
|
|
||
|
|
def __init__(self, chart) -> None:
|
||
|
|
super().__init__()
|
||
|
|
self._chart = weakref.proxy(chart)
|
||
|
|
self._pen = Pen()
|
||
|
|
self._brush = Brush()
|
||
|
|
self._label = ''
|
||
|
|
if hasattr(self, 'SetPen'):
|
||
|
|
self.SetPen(self._pen)
|
||
|
|
if hasattr(self, 'SetBrush'):
|
||
|
|
self.SetBrush(self._brush)
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the plot's color.
|
||
|
|
|
||
|
|
This is the color used by the plot's pen and brush to draw lines and shapes.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the {plot_name}'s color to red.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> plot.color = 'r'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.pen.color
|
||
|
|
|
||
|
|
@color.setter
|
||
|
|
def color(self, val) -> None:
|
||
|
|
self.pen.color = val
|
||
|
|
self.brush.color = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def pen(self): # numpydoc ignore=RT01
|
||
|
|
"""Pen object controlling how lines in this plot are drawn.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
Pen
|
||
|
|
Pen object controlling how lines in this plot are drawn.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Increase the line width of the {plot_name}'s pen object.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> plot.line_style = '-' # Make sure all lines are visible
|
||
|
|
>>> plot.pen.width = 10
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._pen
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def brush(self): # numpydoc ignore=RT01
|
||
|
|
"""Brush object controlling how shapes in this plot are filled.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
Brush
|
||
|
|
Brush object controlling how shapes in this plot are filled.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Use a custom texture for the {plot_name}'s brush object.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> from pyvista import examples
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> plot.brush.texture = examples.download_puppy_texture()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._brush
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def line_width(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the line width of all lines drawn in this plot.
|
||
|
|
|
||
|
|
This is equivalent to accessing/modifying the width of this plot's pen.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the line width to 10
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> plot.line_style = '-' # Make sure all lines are visible
|
||
|
|
>>> plot.line_width = 10
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.pen.width
|
||
|
|
|
||
|
|
@line_width.setter
|
||
|
|
def line_width(self, val) -> None:
|
||
|
|
self.pen.width = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def line_style(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the line style of all lines drawn in this plot.
|
||
|
|
|
||
|
|
This is equivalent to accessing/modifying the style of this plot's pen.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set a custom line style.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> plot.line_style = '-.'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.pen.style
|
||
|
|
|
||
|
|
@line_style.setter
|
||
|
|
def line_style(self, val) -> None:
|
||
|
|
self.pen.style = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def label(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the this plot's label, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {plot_name} with custom label.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> plot.label = 'My awesome plot'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._label
|
||
|
|
|
||
|
|
@label.setter
|
||
|
|
def label(self, val) -> None:
|
||
|
|
self._label = '' if val is None else val
|
||
|
|
self.SetLabel(self._label) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the this plot's visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {plot_name}.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Hide it.
|
||
|
|
|
||
|
|
>>> plot.visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetVisible() # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@visible.setter
|
||
|
|
def visible(self, val) -> None:
|
||
|
|
self.SetVisible(val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@doc_subs
|
||
|
|
def toggle(self) -> None:
|
||
|
|
"""Toggle the plot's visibility.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {plot_name}.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Hide it.
|
||
|
|
|
||
|
|
>>> plot.toggle()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.visible = not self.visible
|
||
|
|
|
||
|
|
|
||
|
|
class _MultiCompPlot(_Plot):
|
||
|
|
"""Common pythonic interface for :vtk:`vtkPlot` instances with multiple components.
|
||
|
|
|
||
|
|
Example subclasses are BoxPlot, PiePlot, BarPlot and StackPlot.
|
||
|
|
"""
|
||
|
|
|
||
|
|
DEFAULT_COLOR_SCHEME = 'qual_accent'
|
||
|
|
|
||
|
|
# Subclasses should specify following substitutions: 'plot_name', 'chart_init', 'plot_init',
|
||
|
|
# 'multichart_init' and 'multiplot_init'.
|
||
|
|
_DOC_SUBS: dict[str, str] | None = None
|
||
|
|
|
||
|
|
def __init__(self, chart) -> None:
|
||
|
|
super().__init__(chart)
|
||
|
|
self._color_series = _vtk.vtkColorSeries()
|
||
|
|
self._lookup_table = self._color_series.CreateLookupTable(_vtk.vtkColorSeries.CATEGORICAL)
|
||
|
|
self._labels = _vtk.vtkStringArray()
|
||
|
|
self.SetLabels(self._labels) # type: ignore[attr-defined]
|
||
|
|
self.color_scheme = self.DEFAULT_COLOR_SCHEME
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def color_scheme(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the plot's color scheme.
|
||
|
|
|
||
|
|
This scheme defines the colors of the different
|
||
|
|
components drawn by this plot.
|
||
|
|
See the table below for the available color
|
||
|
|
schemes.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
.. _plot_color_schemes:
|
||
|
|
|
||
|
|
Overview of all available color schemes.
|
||
|
|
|
||
|
|
.. include:: ../plot_color_schemes.rst
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the {plot_name}'s color scheme to warm.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {multichart_init}
|
||
|
|
>>> plot = {multiplot_init}
|
||
|
|
>>> plot.color_scheme = 'warm'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return SCHEME_NAMES.get(self._color_series.GetColorScheme(), 'custom')
|
||
|
|
|
||
|
|
@color_scheme.setter
|
||
|
|
def color_scheme(self, val) -> None:
|
||
|
|
self._color_series.SetColorScheme(COLOR_SCHEMES.get(val, COLOR_SCHEMES['custom'])['id']) # type: ignore[index]
|
||
|
|
self._color_series.BuildLookupTable(self._lookup_table, _vtk.vtkColorSeries.CATEGORICAL)
|
||
|
|
self.brush.color = self.colors[0]
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def colors(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the plot's colors.
|
||
|
|
|
||
|
|
These are the colors used for the different
|
||
|
|
components drawn by this plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the {plot_name}'s colors manually.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {multichart_init}
|
||
|
|
>>> plot = {multiplot_init}
|
||
|
|
>>> plot.colors = ['b', 'g', 'r', 'c']
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return [
|
||
|
|
Color(self._color_series.GetColor(i))
|
||
|
|
for i in range(self._color_series.GetNumberOfColors())
|
||
|
|
]
|
||
|
|
|
||
|
|
@colors.setter
|
||
|
|
def colors(self, val):
|
||
|
|
if val is None:
|
||
|
|
self.color_scheme = self.DEFAULT_COLOR_SCHEME
|
||
|
|
# Setting color_scheme already sets brush.color
|
||
|
|
elif isinstance(val, str):
|
||
|
|
self.color_scheme = val
|
||
|
|
# Setting color_scheme already sets brush.color
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
self._color_series.SetNumberOfColors(len(val))
|
||
|
|
for i, color in enumerate(val):
|
||
|
|
self._color_series.SetColor(i, Color(color).vtk_c3ub)
|
||
|
|
self._color_series.BuildLookupTable(
|
||
|
|
self._lookup_table,
|
||
|
|
_vtk.vtkColorSeries.CATEGORICAL,
|
||
|
|
)
|
||
|
|
self.brush.color = self.colors[0] # Synchronize "color" and "colors" properties
|
||
|
|
except ValueError as e:
|
||
|
|
self.color_scheme = self.DEFAULT_COLOR_SCHEME
|
||
|
|
msg = 'Invalid colors specified, falling back to default color scheme.'
|
||
|
|
raise ValueError(msg) from e
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the plot's color.
|
||
|
|
|
||
|
|
This is the color used by the plot's brush
|
||
|
|
to draw the different components.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the {plot_name}'s color to red.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> plot.color = 'r'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.brush.color
|
||
|
|
|
||
|
|
@color.setter
|
||
|
|
def color(self, val) -> None:
|
||
|
|
# Override default _Plot behaviour. This makes sure the plot's "color_scheme", "colors"
|
||
|
|
# and "color" properties (and their internal representations through color series,
|
||
|
|
# lookup tables and brushes) stay synchronized.
|
||
|
|
self.colors = [val]
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def labels(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the this plot's labels, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {plot_name}.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = {multichart_init}
|
||
|
|
>>> plot = {multiplot_init}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Modify the labels.
|
||
|
|
|
||
|
|
>>> plot.labels = ['A', 'B', 'C', 'D']
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return [self._labels.GetValue(i) for i in range(self._labels.GetNumberOfValues())]
|
||
|
|
|
||
|
|
@labels.setter
|
||
|
|
def labels(self, val):
|
||
|
|
self._labels.Reset()
|
||
|
|
if isinstance(val, str):
|
||
|
|
val = [val]
|
||
|
|
try:
|
||
|
|
if val is not None:
|
||
|
|
for label in val:
|
||
|
|
self._labels.InsertNextValue(label)
|
||
|
|
except TypeError:
|
||
|
|
msg = 'Invalid labels specified.'
|
||
|
|
raise ValueError(msg)
|
||
|
|
|
||
|
|
@property
|
||
|
|
@doc_subs
|
||
|
|
def label(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the this plot's label, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a {plot_name} with custom label.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> chart = {chart_init}
|
||
|
|
>>> plot = {plot_init}
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Modify the label.
|
||
|
|
|
||
|
|
>>> plot.label = 'My awesome plot'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.labels[0] if self._labels.GetNumberOfValues() > 0 else ''
|
||
|
|
|
||
|
|
@label.setter
|
||
|
|
def label(self, val) -> None:
|
||
|
|
# Override default _Plot behaviour. This makes sure the plot's "labels" and "label"
|
||
|
|
# properties (and their internal representations) stay synchronized.
|
||
|
|
self.labels = None if val is None else [val]
|
||
|
|
|
||
|
|
|
||
|
|
class LinePlot2D(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _Plot, _vtk.vtkPlotLine):
|
||
|
|
"""Class representing a 2D line plot.
|
||
|
|
|
||
|
|
Users should typically not directly create new plot instances, but use the dedicated 2D
|
||
|
|
chart's plotting methods.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart : Chart2D
|
||
|
|
The chart containing this plot.
|
||
|
|
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points through which a line should be drawn.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
Y coordinates of the points through which a line should be drawn.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the line drawn in this plot. Any color parsable by :class:`pyvista.Color`
|
||
|
|
is allowed.
|
||
|
|
|
||
|
|
width : float, default: 1
|
||
|
|
Width of the line drawn in this plot.
|
||
|
|
|
||
|
|
style : str, default: "-"
|
||
|
|
Style of the line drawn in this plot. See :ref:`Pen.LINE_STYLES <pen_line_styles>`
|
||
|
|
for a list of allowed line styles.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart plotting an approximate satellite
|
||
|
|
trajectory.
|
||
|
|
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> from pyvista import examples
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> x = np.linspace(0, 1, 100)
|
||
|
|
>>> y = np.sin(6.5 * x - 1)
|
||
|
|
>>> _ = chart.line(x, y, color='y', width=4)
|
||
|
|
>>> chart.background_texture = examples.load_globe_texture()
|
||
|
|
>>> chart.hide_axes()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'plot_name': '2D line plot',
|
||
|
|
'chart_init': 'pv.Chart2D()',
|
||
|
|
'plot_init': 'chart.line([0, 1, 2], [2, 1, 3])',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['chart', 'x', 'y'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
chart,
|
||
|
|
x,
|
||
|
|
y,
|
||
|
|
color='b',
|
||
|
|
width=1.0,
|
||
|
|
style='-',
|
||
|
|
label='',
|
||
|
|
) -> None: # numpydoc ignore=PR01,RT01
|
||
|
|
"""Initialize a new 2D line plot instance."""
|
||
|
|
super().__init__(chart)
|
||
|
|
self._table = pyvista.Table({'x': np.empty(0, np.float32), 'y': np.empty(0, np.float32)})
|
||
|
|
self.SetInputData(self._table, 'x', 'y')
|
||
|
|
self.update(x, y)
|
||
|
|
self.color = color
|
||
|
|
self.line_width = width
|
||
|
|
self.line_style = style
|
||
|
|
self.label = label
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the X coordinates of the points through which a line is drawn.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a line plot and display the x coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> plot.x
|
||
|
|
pyvista_ndarray([0, 1, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['x']
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the Y coordinates of the points through which a line is drawn.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a line plot and display the y coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> plot.y
|
||
|
|
pyvista_ndarray([2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['y']
|
||
|
|
|
||
|
|
def update(self, x, y) -> None:
|
||
|
|
"""Update this plot's points, through which a line is drawn.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
The new x coordinates of the points through which a line should be drawn.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
The new y coordinates of the points through which a line should be drawn.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a line plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the line's y coordinates.
|
||
|
|
|
||
|
|
>>> plot.update([0, 1, 2], [3, 1, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if len(x) > 1:
|
||
|
|
self._table.update({'x': np.asarray(x), 'y': np.asarray(y)})
|
||
|
|
self.visible = True
|
||
|
|
else:
|
||
|
|
# Turn off visibility for fewer than 2 points as otherwise an error message is shown
|
||
|
|
self.visible = False
|
||
|
|
|
||
|
|
|
||
|
|
class ScatterPlot2D(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _Plot, _vtk.vtkPlotPoints):
|
||
|
|
"""Class representing a 2D scatter plot.
|
||
|
|
|
||
|
|
Users should typically not directly create new plot instances, but use the dedicated 2D
|
||
|
|
chart's plotting methods.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart : Chart2D
|
||
|
|
The chart containing this plot.
|
||
|
|
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points to draw.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
Y coordinates of the points to draw.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the points drawn in this plot. Any color parsable by :class:`pyvista.Color`
|
||
|
|
is allowed.
|
||
|
|
|
||
|
|
size : float, default: 10
|
||
|
|
Size of the point markers drawn in this plot.
|
||
|
|
|
||
|
|
style : str, default: "o"
|
||
|
|
Style of the point markers drawn in this plot. See
|
||
|
|
:ref:`ScatterPlot2D.MARKER_STYLES <scatter_marker_styles>`
|
||
|
|
for a list of allowed marker styles.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
.. _scatter_marker_styles:
|
||
|
|
|
||
|
|
MARKER_STYLES : dict
|
||
|
|
Dictionary containing all allowed marker styles as its keys.
|
||
|
|
|
||
|
|
.. include:: ../scatter_marker_styles.rst
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Plot a simple sine wave as a scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> x = np.linspace(0, 2 * np.pi, 20)
|
||
|
|
>>> y = np.sin(x)
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.scatter(x, y)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
MARKER_STYLES: ClassVar[
|
||
|
|
dict[str, dict[str, int | str]]
|
||
|
|
] = { # descr is used in the documentation, set to None to hide it from the docs.
|
||
|
|
'': {'id': _vtk.vtkPlotPoints.NONE, 'descr': 'Hidden'},
|
||
|
|
'x': {'id': _vtk.vtkPlotPoints.CROSS, 'descr': 'Cross'},
|
||
|
|
'+': {'id': _vtk.vtkPlotPoints.PLUS, 'descr': 'Plus'},
|
||
|
|
's': {'id': _vtk.vtkPlotPoints.SQUARE, 'descr': 'Square'},
|
||
|
|
'o': {'id': _vtk.vtkPlotPoints.CIRCLE, 'descr': 'Circle'},
|
||
|
|
'd': {'id': _vtk.vtkPlotPoints.DIAMOND, 'descr': 'Diamond'},
|
||
|
|
}
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'plot_name': '2D scatter plot',
|
||
|
|
'chart_init': 'pv.Chart2D()',
|
||
|
|
'plot_init': 'chart.scatter([0, 1, 2, 3, 4], [2, 1, 3, 4, 2])',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['chart', 'x', 'y'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
chart,
|
||
|
|
x,
|
||
|
|
y,
|
||
|
|
color='b',
|
||
|
|
size=10,
|
||
|
|
style='o',
|
||
|
|
label='',
|
||
|
|
) -> None: # numpydoc ignore=PR01,RT01
|
||
|
|
"""Initialize a new 2D scatter plot instance."""
|
||
|
|
super().__init__(chart)
|
||
|
|
self._table = pyvista.Table({'x': np.empty(0, np.float32), 'y': np.empty(0, np.float32)})
|
||
|
|
self.SetInputData(self._table, 'x', 'y')
|
||
|
|
self.update(x, y)
|
||
|
|
self.color = color
|
||
|
|
self.marker_size = size
|
||
|
|
self.marker_style = style
|
||
|
|
self.label = label
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the X coordinates of this plot's points.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a scatter plot and display the x coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.scatter([0, 1, 2, 3, 4], [2, 1, 3, 4, 2])
|
||
|
|
>>> plot.x
|
||
|
|
pyvista_ndarray([0, 1, 2, 3, 4])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['x']
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the Y coordinates of this plot's points.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a scatter plot and display the y coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.scatter([0, 1, 2, 3, 4], [2, 1, 3, 4, 2])
|
||
|
|
>>> plot.y
|
||
|
|
pyvista_ndarray([2, 1, 3, 4, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['y']
|
||
|
|
|
||
|
|
def update(self, x, y) -> None:
|
||
|
|
"""Update this plot's points.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
The new x coordinates of the points to draw.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
The new y coordinates of the points to draw.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.scatter([0, 1, 2, 3, 4], [2, 1, 3, 4, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the marker locations.
|
||
|
|
|
||
|
|
>>> plot.update([0, 1, 2, 3, 4], [3, 2, 4, 2, 1])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if len(x) > 0:
|
||
|
|
self._table.update({'x': np.asarray(x), 'y': np.asarray(y)})
|
||
|
|
self.visible = True
|
||
|
|
else:
|
||
|
|
self.visible = False
|
||
|
|
|
||
|
|
@property
|
||
|
|
def marker_size(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the plot's marker size.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.scatter([0, 1, 2, 3, 4], [2, 1, 3, 4, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Increase the marker size.
|
||
|
|
|
||
|
|
>>> plot.marker_size = 30
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetMarkerSize()
|
||
|
|
|
||
|
|
@marker_size.setter
|
||
|
|
def marker_size(self, val) -> None:
|
||
|
|
self.SetMarkerSize(val)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def marker_style(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the plot's marker style.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.scatter([0, 1, 2, 3, 4], [2, 1, 3, 4, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Change the marker style.
|
||
|
|
|
||
|
|
>>> plot.marker_style = 'd'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._marker_style
|
||
|
|
|
||
|
|
@marker_style.setter
|
||
|
|
def marker_style(self, val):
|
||
|
|
if val is None:
|
||
|
|
val = ''
|
||
|
|
try:
|
||
|
|
self.SetMarkerStyle(self.MARKER_STYLES[val]['id']) # type: ignore[arg-type]
|
||
|
|
self._marker_style = val
|
||
|
|
except KeyError:
|
||
|
|
formatted_styles = '", "'.join(self.MARKER_STYLES.keys())
|
||
|
|
msg = f'Invalid marker style. Allowed marker styles: "{formatted_styles}"'
|
||
|
|
raise ValueError(msg)
|
||
|
|
|
||
|
|
|
||
|
|
class AreaPlot(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _Plot, _vtk.vtkPlotArea):
|
||
|
|
"""Class representing a 2D area plot.
|
||
|
|
|
||
|
|
Users should typically not directly create new plot instances, but use the dedicated 2D
|
||
|
|
chart's plotting methods.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart : Chart2D
|
||
|
|
The chart containing this plot.
|
||
|
|
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points outlining the area to draw.
|
||
|
|
|
||
|
|
y1 : array_like
|
||
|
|
Y coordinates of the points on the first outline of the area to draw.
|
||
|
|
|
||
|
|
y2 : array_like, optional
|
||
|
|
Y coordinates of the points on the second outline of the area to
|
||
|
|
draw. Defaults to ``numpy.zeros_like(x)``.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the area drawn in this plot. Any color parsable by :class:`pyvista.Color` is
|
||
|
|
allowed.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create an area plot showing the minimum and maximum precipitation observed in each month.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> x = np.arange(12)
|
||
|
|
>>> p_min = [11, 0, 16, 2, 23, 18, 25, 17, 9, 12, 14, 21]
|
||
|
|
>>> p_max = [87, 64, 92, 73, 91, 94, 107, 101, 84, 88, 95, 103]
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.area(x, p_min, p_max)
|
||
|
|
>>> chart.x_axis.tick_locations = x
|
||
|
|
>>> chart.x_axis.tick_labels = [
|
||
|
|
... 'Jan',
|
||
|
|
... 'Feb',
|
||
|
|
... 'Mar',
|
||
|
|
... 'Apr',
|
||
|
|
... 'May',
|
||
|
|
... 'Jun',
|
||
|
|
... 'Jul',
|
||
|
|
... 'Aug',
|
||
|
|
... 'Sep',
|
||
|
|
... 'Oct',
|
||
|
|
... 'Nov',
|
||
|
|
... 'Dec',
|
||
|
|
... ]
|
||
|
|
>>> chart.x_axis.label = 'Month'
|
||
|
|
>>> chart.y_axis.label = 'Precipitation [mm]'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'plot_name': 'area plot',
|
||
|
|
'chart_init': 'pv.Chart2D()',
|
||
|
|
'plot_init': 'chart.area([0, 1, 2], [0, 0, 1], [1, 3, 2])',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['chart', 'x', 'y1', 'y2'], n_allowed=4)
|
||
|
|
def __init__(self, chart, x, y1, y2=None, color='b', label='') -> None: # noqa: PLR0917
|
||
|
|
"""Initialize a new 2D area plot instance."""
|
||
|
|
super().__init__(chart)
|
||
|
|
self._table = pyvista.Table(
|
||
|
|
{
|
||
|
|
'x': np.empty(0, np.float32),
|
||
|
|
'y1': np.empty(0, np.float32),
|
||
|
|
'y2': np.empty(0, np.float32),
|
||
|
|
},
|
||
|
|
)
|
||
|
|
self.SetInputData(self._table)
|
||
|
|
self.SetInputArray(0, 'x')
|
||
|
|
self.SetInputArray(1, 'y1')
|
||
|
|
self.SetInputArray(2, 'y2')
|
||
|
|
self.update(x, y1, y2)
|
||
|
|
self.color = color
|
||
|
|
self.label = label
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the X coordinates of the points outlining the drawn area.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create an area plot and display the x coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [2, 1, 3], [1, 0, 1])
|
||
|
|
>>> plot.x
|
||
|
|
pyvista_ndarray([0, 1, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['x']
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y1(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the Y coordinates of the points on the first outline of the drawn area.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create an area plot and display the y1 coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [2, 1, 3], [1, 0, 1])
|
||
|
|
>>> plot.y1
|
||
|
|
pyvista_ndarray([2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['y1']
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y2(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the Y coordinates of the points on the second outline of the drawn area.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create an area plot and display the y2 coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [2, 1, 3], [1, 0, 1])
|
||
|
|
>>> plot.y2
|
||
|
|
pyvista_ndarray([1, 0, 1])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['y2']
|
||
|
|
|
||
|
|
def update(self, x, y1, y2=None) -> None:
|
||
|
|
"""Update this plot's points, outlining the area to draw.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
The new x coordinates of the points outlining the area.
|
||
|
|
|
||
|
|
y1 : array_like
|
||
|
|
The new y coordinates of the points on the first outline of the area.
|
||
|
|
|
||
|
|
y2 : array_like, optional
|
||
|
|
The new y coordinates of the points on the second outline of the
|
||
|
|
area. Default ``numpy.zeros_like(x)``.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create an area plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the points on the second outline of the area.
|
||
|
|
|
||
|
|
>>> plot.update([0, 1, 2], [2, 1, 3], [1, 0, 1])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if len(x) > 0:
|
||
|
|
if y2 is None:
|
||
|
|
y2 = np.zeros_like(x)
|
||
|
|
self._table.update(
|
||
|
|
{
|
||
|
|
'x': np.asarray(x),
|
||
|
|
'y1': np.asarray(y1),
|
||
|
|
'y2': np.asarray(y2),
|
||
|
|
},
|
||
|
|
)
|
||
|
|
self.visible = True
|
||
|
|
else:
|
||
|
|
self.visible = False
|
||
|
|
|
||
|
|
|
||
|
|
class BarPlot(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _MultiCompPlot, _vtk.vtkPlotBar):
|
||
|
|
"""Class representing a 2D bar plot.
|
||
|
|
|
||
|
|
Users should typically not directly create new plot instances, but use the dedicated 2D
|
||
|
|
chart's plotting methods.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart : Chart2D
|
||
|
|
The chart containing this plot.
|
||
|
|
|
||
|
|
x : array_like
|
||
|
|
Positions (along the x-axis for a vertical orientation, along the y-axis for
|
||
|
|
a horizontal orientation) of the bars to draw.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
Size of the bars to draw. Multiple bars can be stacked by passing a sequence of sequences.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the bars drawn in this plot. Any color parsable by :class:`pyvista.Color` is
|
||
|
|
allowed.
|
||
|
|
|
||
|
|
orientation : str, default: "V"
|
||
|
|
Orientation of the bars drawn in this plot. Either ``"H"`` for an horizontal orientation
|
||
|
|
or ``"V"`` for a vertical orientation.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a stacked bar chart showing the average time spent on activities
|
||
|
|
throughout the week.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> x = np.arange(1, 8)
|
||
|
|
>>> y_s = [7, 8, 7.5, 8, 7.5, 9, 10]
|
||
|
|
>>> y_h = [2, 3, 2, 2.5, 1.5, 4, 6.5]
|
||
|
|
>>> y_w = [8, 8, 7, 8, 7, 0, 0]
|
||
|
|
>>> y_r = [5, 2.5, 4.5, 3.5, 6, 9, 6.5]
|
||
|
|
>>> y_t = [2, 2.5, 3, 2, 2, 2, 1]
|
||
|
|
>>> labels = ['Sleep', 'Household', 'Work', 'Relax', 'Transport']
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.bar(x, [y_s, y_h, y_w, y_r, y_t], label=labels)
|
||
|
|
>>> chart.x_axis.tick_locations = x
|
||
|
|
>>> chart.x_axis.tick_labels = [
|
||
|
|
... 'Mon',
|
||
|
|
... 'Tue',
|
||
|
|
... 'Wed',
|
||
|
|
... 'Thu',
|
||
|
|
... 'Fri',
|
||
|
|
... 'Sat',
|
||
|
|
... 'Sun',
|
||
|
|
... ]
|
||
|
|
>>> chart.x_label = 'Day of week'
|
||
|
|
>>> chart.y_label = 'Average time spent'
|
||
|
|
>>> chart.grid = False # Disable the grid lines
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
ORIENTATIONS: ClassVar[dict[str, int]] = {
|
||
|
|
'H': _vtk.vtkPlotBar.HORIZONTAL,
|
||
|
|
'V': _vtk.vtkPlotBar.VERTICAL,
|
||
|
|
}
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'plot_name': 'bar plot',
|
||
|
|
'chart_init': 'pv.Chart2D()',
|
||
|
|
'plot_init': 'chart.bar([1, 2, 3], [2, 1, 3])',
|
||
|
|
'multichart_init': 'pv.Chart2D()',
|
||
|
|
'multiplot_init': 'chart.bar([1, 2, 3], [[2, 1, 3], [1, 0, 2], [0, 3, 1], [3, 2, 0]])',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['chart', 'x', 'y'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
chart,
|
||
|
|
x,
|
||
|
|
y,
|
||
|
|
color=None,
|
||
|
|
orientation='V',
|
||
|
|
label=None,
|
||
|
|
) -> None: # numpydoc ignore=PR01,RT01
|
||
|
|
"""Initialize a new 2D bar plot instance."""
|
||
|
|
super().__init__(chart)
|
||
|
|
if not isinstance(y[0], (Sequence, np.ndarray)):
|
||
|
|
y = (y,)
|
||
|
|
y_data = {f'y{i}': np.empty(0, np.float32) for i in range(len(y))}
|
||
|
|
self._table = pyvista.Table({'x': np.empty(0, np.float32), **y_data})
|
||
|
|
self.SetInputData(self._table, 'x', 'y0')
|
||
|
|
for i in range(1, len(y)):
|
||
|
|
self.SetInputArray(i + 1, f'y{i}')
|
||
|
|
self.update(x, y)
|
||
|
|
|
||
|
|
if len(y) > 1:
|
||
|
|
self.SetColorSeries(self._color_series)
|
||
|
|
self.colors = color # None will use default scheme
|
||
|
|
self.labels = label
|
||
|
|
else:
|
||
|
|
# Use blue bars by default in single component mode
|
||
|
|
self.color = 'b' if color is None else color
|
||
|
|
self.label = label
|
||
|
|
self.orientation = orientation
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the positions of the drawn bars.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a bar plot and display the positions.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.bar([1, 2, 3], [[2, 1, 3], [1, 2, 0]])
|
||
|
|
>>> plot.x
|
||
|
|
pyvista_ndarray([1, 2, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['x']
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the sizes of the drawn bars.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a bar plot and display the sizes.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.bar([1, 2, 3], [[2, 1, 3], [1, 2, 0]])
|
||
|
|
>>> plot.y
|
||
|
|
(pyvista_ndarray([2, 1, 3]), pyvista_ndarray([1, 2, 0]))
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return tuple(self._table[f'y{i}'] for i in range(self._table.n_arrays - 1))
|
||
|
|
|
||
|
|
def update(self, x, y) -> None:
|
||
|
|
"""Update the positions and/or size of the bars in this plot.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
The new positions of the bars to draw.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
The new sizes of the bars to draw.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a bar plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.bar([1, 2, 3], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the bar sizes.
|
||
|
|
|
||
|
|
>>> plot.update([1, 2, 3], [3, 1, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if len(x) > 0:
|
||
|
|
if not isinstance(y[0], (Sequence, np.ndarray)):
|
||
|
|
y = (y,)
|
||
|
|
y_data = {f'y{i}': np.asarray(y[i]) for i in range(len(y))}
|
||
|
|
self._table.update({'x': np.asarray(x), **y_data})
|
||
|
|
self.visible = True
|
||
|
|
else:
|
||
|
|
self.visible = False
|
||
|
|
|
||
|
|
@property
|
||
|
|
def orientation(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the orientation of the bars in this plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a bar plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.bar([1, 2, 3], [[2, 1, 3], [1, 3, 2]])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Change the orientation to horizontal.
|
||
|
|
|
||
|
|
>>> plot.orientation = 'H'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._orientation
|
||
|
|
|
||
|
|
@orientation.setter
|
||
|
|
def orientation(self, val):
|
||
|
|
try:
|
||
|
|
self.SetOrientation(self.ORIENTATIONS[val])
|
||
|
|
self._orientation = val
|
||
|
|
except KeyError:
|
||
|
|
formatted_orientations = '", "'.join(self.ORIENTATIONS.keys())
|
||
|
|
msg = f'Invalid orientation. Allowed orientations: "{formatted_orientations}"'
|
||
|
|
raise ValueError(msg)
|
||
|
|
|
||
|
|
|
||
|
|
class StackPlot(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _MultiCompPlot, _vtk.vtkPlotStacked):
|
||
|
|
"""Class representing a 2D stack plot.
|
||
|
|
|
||
|
|
Users should typically not directly create new plot instances, but use the dedicated 2D
|
||
|
|
chart's plotting methods.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart : Chart2D
|
||
|
|
The chart containing this plot.
|
||
|
|
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points outlining the stacks (areas) to draw.
|
||
|
|
|
||
|
|
ys : sequence[array_like]
|
||
|
|
Size of the stacks (areas) to draw at the corresponding X
|
||
|
|
coordinates. Each sequence defines the sizes of one stack
|
||
|
|
(area), which are stacked on top of each other.
|
||
|
|
|
||
|
|
colors : sequence[ColorLike], optional
|
||
|
|
Color of the stacks (areas) drawn in this plot. Any color
|
||
|
|
parsable by :class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
labels : sequence[str], default: []
|
||
|
|
Label for each stack (area) drawn in this plot, as shown in
|
||
|
|
the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a stack plot showing the amount of vehicles sold per type.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> year = [f'{y}' for y in np.arange(2011, 2021)]
|
||
|
|
>>> x = np.arange(len(year))
|
||
|
|
>>> n_e = [1739, 4925, 9515, 21727, 31452, 29926, 40648, 57761, 76370, 93702]
|
||
|
|
>>> n_h = [5563, 7642, 11937, 13905, 22807, 46700, 60875, 53689, 46650, 50321]
|
||
|
|
>>> n_f = [
|
||
|
|
... 166556,
|
||
|
|
... 157249,
|
||
|
|
... 151552,
|
||
|
|
... 138183,
|
||
|
|
... 129669,
|
||
|
|
... 113985,
|
||
|
|
... 92965,
|
||
|
|
... 73683,
|
||
|
|
... 57097,
|
||
|
|
... 29499,
|
||
|
|
... ]
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.stack(x, [n_e, n_h, n_f])
|
||
|
|
>>> plot.labels = ['Electric', 'Hybrid', 'Fossil']
|
||
|
|
>>> chart.x_axis.label = 'Year'
|
||
|
|
>>> chart.x_axis.tick_locations = x
|
||
|
|
>>> chart.x_axis.tick_labels = year
|
||
|
|
>>> chart.y_axis.label = 'New car sales'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'plot_name': 'stack plot',
|
||
|
|
'chart_init': 'pv.Chart2D()',
|
||
|
|
'plot_init': 'chart.stack([0, 1, 2], [2, 1, 3])',
|
||
|
|
'multichart_init': 'pv.Chart2D()',
|
||
|
|
'multiplot_init': 'chart.stack([0, 1, 2], [[2, 1, 3], [1, 0, 2], [0, 3, 1], [3, 2, 0]])',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['chart', 'x', 'ys'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self, chart, x, ys, colors=None, labels=None
|
||
|
|
) -> None:
|
||
|
|
"""Initialize a new 2D stack plot instance."""
|
||
|
|
super().__init__(chart)
|
||
|
|
if not isinstance(ys[0], (Sequence, np.ndarray)):
|
||
|
|
ys = (ys,)
|
||
|
|
y_data = {f'y{i}': np.empty(0, np.float32) for i in range(len(ys))}
|
||
|
|
self._table = pyvista.Table({'x': np.empty(0, np.float32), **y_data})
|
||
|
|
self.SetInputData(self._table, 'x', 'y0')
|
||
|
|
for i in range(1, len(ys)):
|
||
|
|
self.SetInputArray(i + 1, f'y{i}')
|
||
|
|
self.update(x, ys)
|
||
|
|
|
||
|
|
if len(ys) > 1:
|
||
|
|
self.SetColorSeries(self._color_series)
|
||
|
|
self.colors = colors # None will use default scheme
|
||
|
|
self.labels = labels
|
||
|
|
else:
|
||
|
|
self.color = 'b' if colors is None else colors
|
||
|
|
self.label = labels
|
||
|
|
self.pen.style = None # Hide lines by default
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the X coordinates of the drawn stacks.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a stack plot and display the x coordinates.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.stack([0, 1, 2], [[2, 1, 3], [1, 2, 0]])
|
||
|
|
>>> plot.x
|
||
|
|
pyvista_ndarray([0, 1, 2])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table['x']
|
||
|
|
|
||
|
|
@property
|
||
|
|
def ys(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the sizes of the drawn stacks.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a stack plot and display the sizes.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.stack([0, 1, 2], [[2, 1, 3], [1, 2, 0]])
|
||
|
|
>>> plot.ys
|
||
|
|
(pyvista_ndarray([2, 1, 3]), pyvista_ndarray([1, 2, 0]))
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return tuple(self._table[f'y{i}'] for i in range(self._table.n_arrays - 1))
|
||
|
|
|
||
|
|
def update(self, x, ys) -> None:
|
||
|
|
"""Update the locations and/or size of the stacks (areas) in this plot.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
The new x coordinates of the stacks (areas) to draw.
|
||
|
|
|
||
|
|
ys : sequence[array_like]
|
||
|
|
The new sizes of the stacks (areas) to draw.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a stack plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.stack([0, 1, 2], [[2, 1, 3], [1, 2, 1]])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the stack sizes.
|
||
|
|
|
||
|
|
>>> plot.update([0, 1, 2], [[3, 1, 2], [0, 3, 1]])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if len(x) > 0:
|
||
|
|
if not isinstance(ys[0], (Sequence, np.ndarray)):
|
||
|
|
ys = (ys,)
|
||
|
|
y_data = {f'y{i}': np.asarray(ys[i]) for i in range(len(ys))}
|
||
|
|
self._table.update({'x': np.asarray(x), **y_data})
|
||
|
|
self.visible = True
|
||
|
|
else:
|
||
|
|
self.visible = False
|
||
|
|
|
||
|
|
|
||
|
|
class Chart2D(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _Chart, _vtk.vtkChartXY):
|
||
|
|
"""2D chart class similar to a ``matplotlib`` figure.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
size : sequence[float], default: (1, 1)
|
||
|
|
Size of the chart in normalized coordinates. A size of ``(0,
|
||
|
|
0)`` is invisible, a size of ``(1, 1)`` occupies the whole
|
||
|
|
renderer's width and height.
|
||
|
|
|
||
|
|
loc : sequence[float], default: (0, 0)
|
||
|
|
Location of the chart (its bottom left corner) in normalized
|
||
|
|
coordinates. A location of ``(0, 0)`` corresponds to the
|
||
|
|
renderer's bottom left corner, a location of ``(1, 1)``
|
||
|
|
corresponds to the renderer's top right corner.
|
||
|
|
|
||
|
|
x_label : str, default: "x"
|
||
|
|
Label along the x-axis.
|
||
|
|
|
||
|
|
y_label : str, default: "y"
|
||
|
|
Label along the y-axis.
|
||
|
|
|
||
|
|
grid : bool, default: True
|
||
|
|
Show the background grid in the plot.
|
||
|
|
|
||
|
|
See Also
|
||
|
|
--------
|
||
|
|
:ref:`chart_basics_example`
|
||
|
|
:ref:`chart_overlays_example`
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Plot a simple sine wave as a scatter and line plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> x = np.linspace(0, 2 * np.pi, 20)
|
||
|
|
>>> y = np.sin(x)
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.scatter(x, y)
|
||
|
|
>>> _ = chart.line(x, y, color='r')
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Combine multiple types of plots in the same chart.
|
||
|
|
|
||
|
|
>>> rng = np.random.default_rng(1)
|
||
|
|
>>> x = np.arange(1, 8)
|
||
|
|
>>> y = rng.integers(5, 15, 7)
|
||
|
|
>>> e = np.abs(rng.normal(scale=2, size=7))
|
||
|
|
>>> z = rng.integers(0, 5, 7)
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.area(x, y - e, y + e, color=(0.12, 0.46, 0.71, 0.2))
|
||
|
|
>>> _ = chart.line(x, y, color='tab:blue', style='--', label='Scores')
|
||
|
|
>>> _ = chart.scatter(x, y, color='tab:blue', style='d')
|
||
|
|
>>> _ = chart.bar(x, z, color='tab:orange', label='Violations')
|
||
|
|
>>> chart.x_axis.tick_locations = x
|
||
|
|
>>> chart.x_axis.tick_labels = [
|
||
|
|
... 'Mon',
|
||
|
|
... 'Tue',
|
||
|
|
... 'Wed',
|
||
|
|
... 'Thu',
|
||
|
|
... 'Fri',
|
||
|
|
... 'Sat',
|
||
|
|
... 'Sun',
|
||
|
|
... ]
|
||
|
|
>>> chart.x_label = 'Day of week'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
PLOT_TYPES: ClassVar[
|
||
|
|
dict[
|
||
|
|
str,
|
||
|
|
(type[ScatterPlot2D | LinePlot2D | AreaPlot | BarPlot | StackPlot]),
|
||
|
|
]
|
||
|
|
] = {
|
||
|
|
'scatter': ScatterPlot2D,
|
||
|
|
'line': LinePlot2D,
|
||
|
|
'area': AreaPlot,
|
||
|
|
'bar': BarPlot,
|
||
|
|
'stack': StackPlot,
|
||
|
|
}
|
||
|
|
_PLOT_CLASSES: ClassVar[
|
||
|
|
dict[
|
||
|
|
(type[ScatterPlot2D | LinePlot2D | AreaPlot | BarPlot | StackPlot]),
|
||
|
|
str,
|
||
|
|
]
|
||
|
|
] = {plot_class: plot_type for (plot_type, plot_class) in PLOT_TYPES.items()}
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'chart_name': '2D chart',
|
||
|
|
'chart_args': '',
|
||
|
|
'chart_init': """
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])""",
|
||
|
|
'chart_set_labels': 'plot.label = "My awesome plot"',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
size=(1, 1),
|
||
|
|
loc=(0, 0),
|
||
|
|
x_label='x',
|
||
|
|
y_label='y',
|
||
|
|
grid: bool = True, # noqa: FBT001, FBT002
|
||
|
|
) -> None: # numpydoc ignore=PR01,RT01
|
||
|
|
"""Initialize the chart."""
|
||
|
|
super().__init__(size, loc)
|
||
|
|
self._plots = {plot_type: [] for plot_type in self.PLOT_TYPES.keys()} # type: ignore[var-annotated]
|
||
|
|
self.SetAutoSize(False) # We manually set the appropriate size
|
||
|
|
# Overwrite custom x-axis and y-axis using a wrapper object, as using the
|
||
|
|
# SetAxis method causes a crash at the end of the script's execution (nonzero exit code).
|
||
|
|
self._x_axis = Axis(_wrap=self.GetAxis(_vtk.vtkAxis.BOTTOM)) # type: ignore[call-arg]
|
||
|
|
self._y_axis = Axis(_wrap=self.GetAxis(_vtk.vtkAxis.LEFT)) # type: ignore[call-arg]
|
||
|
|
# Note: registering the axis prevents the nonzero exit code at the end, however
|
||
|
|
# this results in memory leaks in the plotting tests.
|
||
|
|
# self.SetAxis(_vtk.vtkAxis.BOTTOM, self._x_axis)
|
||
|
|
# self.SetAxis(_vtk.vtkAxis.LEFT, self._y_axis)
|
||
|
|
# self.Register(self._x_axis)
|
||
|
|
# self.Register(self._y_axis)
|
||
|
|
self.x_label = x_label
|
||
|
|
self.y_label = y_label
|
||
|
|
self.grid = grid
|
||
|
|
self.legend_visible = True
|
||
|
|
|
||
|
|
def _render_event(self, *args, plotter_render: bool = False, **kwargs) -> None:
|
||
|
|
if plotter_render:
|
||
|
|
# TODO: should probably be called internally by VTK when plot data or axis
|
||
|
|
# behavior/logscale is changed?
|
||
|
|
self.RecalculateBounds()
|
||
|
|
super()._render_event(*args, plotter_render=plotter_render, **kwargs)
|
||
|
|
|
||
|
|
def _add_plot(self, plot_type, *args, **kwargs):
|
||
|
|
"""Add a plot of the given type to this chart."""
|
||
|
|
plot = self.PLOT_TYPES[plot_type](self, *args, **kwargs)
|
||
|
|
self.AddPlot(plot)
|
||
|
|
self._plots[plot_type].append(plot)
|
||
|
|
return plot
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def _parse_format(cls, fmt):
|
||
|
|
"""Parse a format string and separate it into a marker style, line style and color.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
fmt : str
|
||
|
|
Format string to parse. A format string consists of any
|
||
|
|
combination of a valid marker style, a valid line style
|
||
|
|
and parsable color. The specific order does not
|
||
|
|
matter. See :attr:`pyvista.ScatterPlot2D.MARKER_STYLES`
|
||
|
|
for a list of valid marker styles,
|
||
|
|
:attr:`pyvista.Pen.LINE_STYLES` for a list of valid line
|
||
|
|
styles and :class:`pyvista.Color` for an overview of
|
||
|
|
parsable colors.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
marker_style : str
|
||
|
|
Extracted marker style (empty string if no marker style
|
||
|
|
was present in the format string).
|
||
|
|
|
||
|
|
line_style : str
|
||
|
|
Extracted line style (empty string if no line style was
|
||
|
|
present in the format string).
|
||
|
|
|
||
|
|
color : str
|
||
|
|
Extracted color string (defaults to ``"b"`` if no color
|
||
|
|
was present in the format string).
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> m, l, c = pv.Chart2D._parse_format('x--b')
|
||
|
|
|
||
|
|
"""
|
||
|
|
marker_style = ''
|
||
|
|
line_style = ''
|
||
|
|
color = None
|
||
|
|
# Note: All colors, marker styles and line styles are sorted in decreasing order of length
|
||
|
|
# to be able to find the largest match first (e.g. find 'darkred' and '--' first instead
|
||
|
|
# of 'red' and '-')
|
||
|
|
colors = sorted(
|
||
|
|
itertools.chain(hexcolors.keys(), color_synonyms.keys()),
|
||
|
|
key=len,
|
||
|
|
reverse=True,
|
||
|
|
)
|
||
|
|
marker_styles = sorted(ScatterPlot2D.MARKER_STYLES.keys(), key=len, reverse=True)
|
||
|
|
line_styles = sorted(Pen.LINE_STYLES.keys(), key=len, reverse=True)
|
||
|
|
hex_pattern = '(#|0x)[A-Fa-f0-9]{6}([A-Fa-f0-9]{2})?' # Match RGB(A) hex string
|
||
|
|
# Extract color from format string
|
||
|
|
match = re.search(hex_pattern, fmt) # Start with matching hex strings
|
||
|
|
if match is not None:
|
||
|
|
color = match.group()
|
||
|
|
else: # Proceed with matching color strings
|
||
|
|
for c in colors:
|
||
|
|
if c in fmt:
|
||
|
|
color = c
|
||
|
|
break
|
||
|
|
if color is not None:
|
||
|
|
fmt = fmt.replace(color, '', 1) # Remove found color from format string
|
||
|
|
else:
|
||
|
|
color = 'b'
|
||
|
|
# Extract marker style from format string
|
||
|
|
for style in marker_styles[:-1]: # Last style is empty string
|
||
|
|
if style in fmt:
|
||
|
|
marker_style = style
|
||
|
|
fmt = fmt.replace(
|
||
|
|
marker_style,
|
||
|
|
'',
|
||
|
|
1,
|
||
|
|
) # Remove found marker_style from format string
|
||
|
|
break
|
||
|
|
# Extract line style from format string
|
||
|
|
for style in line_styles[:-1]: # Last style is empty string
|
||
|
|
if style in fmt:
|
||
|
|
line_style = style
|
||
|
|
fmt = fmt.replace(line_style, '', 1) # Remove found line_style from format string
|
||
|
|
break
|
||
|
|
return marker_style, line_style, color
|
||
|
|
|
||
|
|
def plot(self, x, y=None, fmt='-'):
|
||
|
|
"""Matplotlib like plot method.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
Values to plot on the X-axis. In case ``y`` is ``None``,
|
||
|
|
these are the values to plot on the Y-axis instead.
|
||
|
|
|
||
|
|
y : array_like, optional
|
||
|
|
Values to plot on the Y-axis.
|
||
|
|
|
||
|
|
fmt : str, default: "-"
|
||
|
|
A format string, e.g. ``'ro'`` for red circles. See the Notes
|
||
|
|
section for a full description of the format strings.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
scatter_plot : plotting.charts.ScatterPlot2D, optional
|
||
|
|
The created scatter plot when a valid marker style
|
||
|
|
was present in the format string, ``None`` otherwise.
|
||
|
|
|
||
|
|
line_plot : plotting.charts.LinePlot2D, optional
|
||
|
|
The created line plot when a valid line style was
|
||
|
|
present in the format string, ``None`` otherwise.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
This plot method shares many of the same plotting features as
|
||
|
|
the `matplotlib.pyplot.plot
|
||
|
|
<https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html>`_.
|
||
|
|
Please reference the documentation there for a full
|
||
|
|
description of the allowable format strings.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Generate a line plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _, line_plot = chart.plot(range(10), range(10))
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Generate a line and scatter plot.
|
||
|
|
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> scatter_plot, line_plot = chart.plot(range(10), fmt='o-')
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if y is None:
|
||
|
|
y = x
|
||
|
|
x = np.arange(len(y))
|
||
|
|
elif isinstance(y, str):
|
||
|
|
fmt = y
|
||
|
|
y = x
|
||
|
|
x = np.arange(len(y))
|
||
|
|
marker_style, line_style, color = self._parse_format(fmt)
|
||
|
|
scatter_plot, line_plot = None, None
|
||
|
|
if marker_style != '':
|
||
|
|
scatter_plot = self.scatter(x, y, color=color, style=marker_style)
|
||
|
|
if line_style != '':
|
||
|
|
line_plot = self.line(x, y, color=color, style=line_style)
|
||
|
|
return scatter_plot, line_plot
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['x', 'y'])
|
||
|
|
def scatter(self, x, y, color='b', size=10, style='o', label=''): # noqa: PLR0917
|
||
|
|
"""Add a scatter plot to this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points to draw.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
Y coordinates of the points to draw.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the points drawn in this plot. Any color parsable
|
||
|
|
by :class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
size : float, default: 10
|
||
|
|
Size of the point markers drawn in this plot.
|
||
|
|
|
||
|
|
style : str, default: "o"
|
||
|
|
Style of the point markers drawn in this plot. See
|
||
|
|
:ref:`ScatterPlot2D.MARKER_STYLES <scatter_marker_styles>`
|
||
|
|
for a list of allowed marker styles.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
plotting.charts.ScatterPlot2D
|
||
|
|
The created scatter plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Generate a scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.scatter([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._add_plot('scatter', x, y, color=color, size=size, style=style, label=label)
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['x', 'y'])
|
||
|
|
def line(self, x, y, color='b', width=1.0, style='-', label=''): # noqa: PLR0917
|
||
|
|
"""Add a line plot to this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points through which a line should be drawn.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
Y coordinates of the points through which a line should be drawn.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the line drawn in this plot. Any color parsable
|
||
|
|
by :class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
width : float, default: 1
|
||
|
|
Width of the line drawn in this plot.
|
||
|
|
|
||
|
|
style : str, default: "-"
|
||
|
|
Style of the line drawn in this plot. See
|
||
|
|
:ref:`Pen.LINE_STYLES <pen_line_styles>` for a list of
|
||
|
|
allowed line styles.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
plotting.charts.LinePlot2D
|
||
|
|
The created line plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Generate a line plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._add_plot('line', x, y, color=color, width=width, style=style, label=label)
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['x', 'y1', 'y2'])
|
||
|
|
def area( # noqa: PLR0917
|
||
|
|
self, x, y1, y2=None, color='b', label=''
|
||
|
|
):
|
||
|
|
"""Add an area plot to this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points outlining the area to draw.
|
||
|
|
|
||
|
|
y1 : array_like
|
||
|
|
Y coordinates of the points on the first outline of the area to draw.
|
||
|
|
|
||
|
|
y2 : array_like, optional
|
||
|
|
Y coordinates of the points on the second outline of the
|
||
|
|
area to draw. Defaults to ``np.zeros_like(x)``.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the area drawn in this plot. Any color parsable
|
||
|
|
by :class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
plotting.charts.AreaPlot
|
||
|
|
The created area plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Generate an area plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.area([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._add_plot('area', x, y1, y2, color=color, label=label)
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['x', 'y'])
|
||
|
|
def bar( # noqa: PLR0917
|
||
|
|
self, x, y, color=None, orientation='V', label=None
|
||
|
|
):
|
||
|
|
"""Add a bar plot to this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
Positions (along the x-axis for a vertical orientation,
|
||
|
|
along the y-axis for a horizontal orientation) of the bars
|
||
|
|
to draw.
|
||
|
|
|
||
|
|
y : array_like
|
||
|
|
Size of the bars to draw. Multiple bars can be stacked by
|
||
|
|
passing a sequence of sequences.
|
||
|
|
|
||
|
|
color : ColorLike, default: "b"
|
||
|
|
Color of the bars drawn in this plot. Any color parsable
|
||
|
|
by :class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
orientation : str, default: "V"
|
||
|
|
Orientation of the bars drawn in this plot. Either ``"H"``
|
||
|
|
for an horizontal orientation or ``"V"`` for a vertical
|
||
|
|
orientation.
|
||
|
|
|
||
|
|
label : str, default: ""
|
||
|
|
Label of this plot, as shown in the chart's legend.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
plotting.charts.BarPlot
|
||
|
|
The created bar plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Generate a bar plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.bar([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._add_plot('bar', x, y, color=color, orientation=orientation, label=label)
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['x', 'ys'])
|
||
|
|
def stack( # noqa: PLR0917
|
||
|
|
self, x, ys, colors=None, labels=None
|
||
|
|
):
|
||
|
|
"""Add a stack plot to this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
x : array_like
|
||
|
|
X coordinates of the points outlining the stacks (areas) to draw.
|
||
|
|
|
||
|
|
ys : sequence[array_like]
|
||
|
|
Size of the stacks (areas) to draw at the corresponding X
|
||
|
|
coordinates. Each sequence defines the sizes of one stack
|
||
|
|
(area), which are stacked on top of each other.
|
||
|
|
|
||
|
|
colors : sequence[ColorLike], optional
|
||
|
|
Color of the stacks (areas) drawn in this plot. Any color
|
||
|
|
parsable by :class:`pyvista.Color` is allowed.
|
||
|
|
|
||
|
|
labels : sequence[str], default: []
|
||
|
|
Label for each stack (area) drawn in this plot, as shown
|
||
|
|
in the chart's legend.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
plotting.charts.StackPlot
|
||
|
|
The created stack plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Generate a stack plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> plot = chart.stack([0, 1, 2], [[2, 1, 3], [1, 2, 1]])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._add_plot('stack', x, ys, colors=colors, labels=labels)
|
||
|
|
|
||
|
|
def plots(self, plot_type=None):
|
||
|
|
"""Return all plots of the specified type in this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
plot_type : str, optional
|
||
|
|
The type of plots to return. Allowed types are
|
||
|
|
``"scatter"``, ``"line"``, ``"area"``, ``"bar"``
|
||
|
|
and ``"stack"``.
|
||
|
|
If no type is provided (``None``), all plots are returned,
|
||
|
|
regardless of their type.
|
||
|
|
|
||
|
|
Yields
|
||
|
|
------
|
||
|
|
plot
|
||
|
|
One of the plots (of the specified type) in this chart.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with a line and scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> scatter_plot, line_plot = chart.plot([0, 1, 2], [2, 1, 3], 'o-')
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Retrieve all plots in the chart.
|
||
|
|
|
||
|
|
>>> plots = [*chart.plots()]
|
||
|
|
>>> scatter_plot in plots and line_plot in plots
|
||
|
|
True
|
||
|
|
|
||
|
|
Retrieve all line plots in the chart.
|
||
|
|
|
||
|
|
>>> line_plots = [*chart.plots('line')]
|
||
|
|
>>> line_plot == line_plots[0]
|
||
|
|
True
|
||
|
|
|
||
|
|
"""
|
||
|
|
plot_types = self.PLOT_TYPES.keys() if plot_type is None else [plot_type]
|
||
|
|
for pl_type in plot_types:
|
||
|
|
yield from self._plots[pl_type]
|
||
|
|
|
||
|
|
def remove_plot(self, plot: ScatterPlot2D | LinePlot2D | AreaPlot | BarPlot | StackPlot):
|
||
|
|
"""Remove the given plot from this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
plot
|
||
|
|
The plot to remove.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with a line and scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> scatter_plot, line_plot = chart.plot([0, 1, 2], [2, 1, 3], 'o-')
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Remove the scatter plot from the chart.
|
||
|
|
|
||
|
|
>>> chart.remove_plot(scatter_plot)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
plot_type = self._PLOT_CLASSES[type(plot)]
|
||
|
|
self._plots[plot_type].remove(plot)
|
||
|
|
self.RemovePlotInstance(plot)
|
||
|
|
except (KeyError, ValueError):
|
||
|
|
msg = 'The given plot is not part of this chart.'
|
||
|
|
raise ValueError(msg)
|
||
|
|
|
||
|
|
def clear(self, plot_type=None) -> None:
|
||
|
|
"""Remove all plots of the specified type from this chart.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
plot_type : str, optional
|
||
|
|
The type of the plots to remove. Allowed types are
|
||
|
|
``"scatter"``, ``"line"``, ``"area"``, ``"bar"``
|
||
|
|
and ``"stack"``.
|
||
|
|
|
||
|
|
If no type is provided (``None``), all plots are removed,
|
||
|
|
regardless of their type.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with multiple line and scatter plot.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.plot([0, 1, 2], [2, 1, 3], 'o-b')
|
||
|
|
>>> _ = chart.plot([-2, -1, 0], [3, 1, 2], 'd-r')
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Remove all scatter plots from the chart.
|
||
|
|
|
||
|
|
>>> chart.clear('scatter')
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
plot_types = self.PLOT_TYPES.keys() if plot_type is None else [plot_type]
|
||
|
|
for pl_type in plot_types:
|
||
|
|
# Make a copy, as this list will be modified by remove_plot
|
||
|
|
plots = [*self._plots[pl_type]]
|
||
|
|
for plot in plots:
|
||
|
|
self.remove_plot(plot)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x_axis(self): # numpydoc ignore=RT01
|
||
|
|
"""Return this chart's horizontal (x) :class:`Axis <plotting.charts.Axis>`.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D plot and hide the x-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_axis.toggle()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._x_axis
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y_axis(self): # numpydoc ignore=RT01
|
||
|
|
"""Return this chart's vertical (y) :class:`Axis <plotting.charts.Axis>`.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D plot and hide the y-axis.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.y_axis.toggle()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._y_axis
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x_label(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the label of this chart's x-axis.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D plot and set custom axis labels.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_label = 'Horizontal axis'
|
||
|
|
>>> chart.y_label = 'Vertical axis'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.x_axis.label
|
||
|
|
|
||
|
|
@x_label.setter
|
||
|
|
def x_label(self, val) -> None:
|
||
|
|
self.x_axis.label = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y_label(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the label of this chart's y-axis.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D plot and set custom axis labels.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_label = 'Horizontal axis'
|
||
|
|
>>> chart.y_label = 'Vertical axis'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.y_axis.label
|
||
|
|
|
||
|
|
@y_label.setter
|
||
|
|
def y_label(self, val) -> None:
|
||
|
|
self.y_axis.label = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
def x_range(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the range of this chart's x-axis.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D plot and set custom axis ranges.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_range = [-2, 2]
|
||
|
|
>>> chart.y_range = [0, 5]
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.x_axis.range
|
||
|
|
|
||
|
|
@x_range.setter
|
||
|
|
def x_range(self, val) -> None:
|
||
|
|
self.x_axis.range = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
def y_range(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the range of this chart's y-axis.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D plot and set custom axis ranges.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.x_range = [-2, 2]
|
||
|
|
>>> chart.y_range = [0, 5]
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.y_axis.range
|
||
|
|
|
||
|
|
@y_range.setter
|
||
|
|
def y_range(self, val) -> None:
|
||
|
|
self.y_axis.range = val
|
||
|
|
|
||
|
|
@property
|
||
|
|
def grid(self): # numpydoc ignore=RT01
|
||
|
|
"""Enable or disable the chart grid.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D chart with the grid disabled.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> x = np.linspace(0, 2 * np.pi, 20)
|
||
|
|
>>> y = np.sin(x)
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line(x, y, color='r')
|
||
|
|
>>> chart.grid = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Enable the grid
|
||
|
|
|
||
|
|
>>> chart.grid = True
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.x_axis.grid and self.y_axis.grid
|
||
|
|
|
||
|
|
@grid.setter
|
||
|
|
def grid(self, val) -> None:
|
||
|
|
self.x_axis.grid = val
|
||
|
|
self.y_axis.grid = val
|
||
|
|
|
||
|
|
def hide_axes(self) -> None:
|
||
|
|
"""Hide the x- and y-axis of this chart.
|
||
|
|
|
||
|
|
This includes all labels, ticks and the grid.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a 2D plot and hide the axes.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.Chart2D()
|
||
|
|
>>> _ = chart.line([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart.hide_axes()
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
for axis in (self.x_axis, self.y_axis):
|
||
|
|
axis.visible = False
|
||
|
|
axis.label_visible = False
|
||
|
|
axis.ticks_visible = False
|
||
|
|
axis.tick_labels_visible = False
|
||
|
|
axis.grid = False
|
||
|
|
|
||
|
|
|
||
|
|
class BoxPlot(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _MultiCompPlot, _vtk.vtkPlotBox):
|
||
|
|
"""Class representing a box plot.
|
||
|
|
|
||
|
|
Users should typically not directly create new plot instances, but
|
||
|
|
use the dedicated ``ChartBox`` class.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart : ChartBox
|
||
|
|
The chart containing this plot.
|
||
|
|
|
||
|
|
data : sequence[array_like]
|
||
|
|
Dataset(s) from which the relevant statistics will be
|
||
|
|
calculated used to draw the box plot.
|
||
|
|
|
||
|
|
colors : sequence[ColorLike], optional
|
||
|
|
Color of the boxes drawn in this plot. Any color parsable by
|
||
|
|
:class:`pyvista.Color` is allowed. If omitted (``None``), the
|
||
|
|
default color scheme is used.
|
||
|
|
|
||
|
|
labels : sequence[str], default: []
|
||
|
|
Label for each box drawn in this plot, as shown in the chart's
|
||
|
|
legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create boxplots for datasets sampled from shifted normal distributions.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> rng = np.random.default_rng(
|
||
|
|
... 1
|
||
|
|
... ) # Seeded random number generator used for data generation
|
||
|
|
>>> normal_data = [rng.normal(i, size=50) for i in range(5)]
|
||
|
|
>>> chart = pv.ChartBox(
|
||
|
|
... normal_data, labels=[f'x ~ N({i},1)' for i in range(5)]
|
||
|
|
... )
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'plot_name': 'box plot',
|
||
|
|
'chart_init': 'pv.ChartBox([[0, 1, 1, 2, 3, 3, 4]])',
|
||
|
|
'plot_init': 'chart.plot',
|
||
|
|
'multichart_init': 'pv.ChartBox('
|
||
|
|
'[[0, 1, 1, 2, 3, 4, 5], '
|
||
|
|
'[0, 1, 2, 2, 3, 4, 5], '
|
||
|
|
'[0, 1, 2, 3, 3, 4, 5], '
|
||
|
|
'[0, 1, 2, 3, 4, 4, 5]])',
|
||
|
|
'multiplot_init': 'chart.plot',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['chart', 'data'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self, chart, data, colors=None, labels=None
|
||
|
|
) -> None:
|
||
|
|
"""Initialize a new box plot instance."""
|
||
|
|
super().__init__(chart)
|
||
|
|
self._table = pyvista.Table(
|
||
|
|
{f'data_{i}': np.asarray(d) for i, d in enumerate(data)},
|
||
|
|
)
|
||
|
|
self._quartiles = _vtk.vtkComputeQuartiles()
|
||
|
|
self._quartiles.SetInputData(self._table)
|
||
|
|
self.SetInputData(self._quartiles.GetOutput())
|
||
|
|
self.update(data)
|
||
|
|
self.SetLookupTable(self._lookup_table)
|
||
|
|
self.colors = colors
|
||
|
|
self.labels = labels
|
||
|
|
|
||
|
|
@property
|
||
|
|
def data(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the datasets of which the boxplots are drawn.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a box plot and display the datasets.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartBox([[0, 1, 1, 2, 3, 3, 4]])
|
||
|
|
>>> chart.plot.data
|
||
|
|
(pyvista_ndarray([0, 1, 1, 2, 3, 3, 4]),)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return tuple(self._table[f'data_{i}'] for i in range(self._table.n_arrays))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def stats(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the statistics of the datasets of which the boxplots are drawn.
|
||
|
|
|
||
|
|
The statistics are the quartiles and extremum values.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a box plot and display the statistics.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartBox([[0, 1, 1, 2, 3, 3, 4]])
|
||
|
|
>>> chart.plot.stats
|
||
|
|
(pyvista_ndarray([0., 1., 2., 3., 4.]),)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
stats_table = pyvista.Table(self._quartiles.GetOutput())
|
||
|
|
return tuple(stats_table[f'data_{i}'] for i in range(stats_table.n_arrays))
|
||
|
|
|
||
|
|
def update(self, data) -> None:
|
||
|
|
"""Update the plot's underlying dataset(s).
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
data : sequence[array_like]
|
||
|
|
The new dataset(s) used in this box plot.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a box plot from a standard Gaussian dataset.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> rng = np.random.default_rng(
|
||
|
|
... 1
|
||
|
|
... ) # Seeded random number generator for data generation
|
||
|
|
>>> chart = pv.ChartBox([rng.normal(size=100)])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the box plot (shift the standard Gaussian distribution).
|
||
|
|
|
||
|
|
>>> chart.plot.update([rng.normal(loc=2, size=100)])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
self._table.update({f'data_{i}': np.asarray(d) for i, d in enumerate(data)})
|
||
|
|
self._quartiles.Update()
|
||
|
|
|
||
|
|
|
||
|
|
class ChartBox(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _Chart, _vtk.vtkChartBox):
|
||
|
|
"""Dedicated chart for drawing box plots.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
data : sequence[array_like]
|
||
|
|
Dataset(s) from which the relevant statistics will be
|
||
|
|
calculated used to draw the box plot.
|
||
|
|
|
||
|
|
colors : sequence[ColorLike], optional
|
||
|
|
Color used for each drawn boxplot. If omitted (``None``), the
|
||
|
|
default color scheme is used.
|
||
|
|
|
||
|
|
labels : sequence[str], default: []
|
||
|
|
Label for each drawn boxplot, as shown in the chart's
|
||
|
|
legend.
|
||
|
|
|
||
|
|
size : sequence[float], optional
|
||
|
|
Size of the chart in normalized coordinates. A size of ``(0,
|
||
|
|
0)`` is invisible, a size of ``(1, 1)`` occupies the whole
|
||
|
|
renderer's width and height.
|
||
|
|
|
||
|
|
loc : sequence[float], optional
|
||
|
|
Location of the chart (its bottom left corner) in normalized
|
||
|
|
coordinates. A location of ``(0, 0)`` corresponds to the
|
||
|
|
renderer's bottom left corner, a location of ``(1, 1)``
|
||
|
|
corresponds to the renderer's top right corner.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create boxplots for datasets sampled from shifted normal distributions.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> rng = np.random.default_rng(
|
||
|
|
... 1
|
||
|
|
... ) # Seeded random number generator used for data generation
|
||
|
|
>>> normal_data = [rng.normal(i, size=50) for i in range(5)]
|
||
|
|
>>> chart = pv.ChartBox(
|
||
|
|
... normal_data, labels=[f'x ~ N({i},1)' for i in range(5)]
|
||
|
|
... )
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'chart_name': 'boxplot chart',
|
||
|
|
'chart_args': '[[0, 1, 1, 2, 3, 3, 4]]',
|
||
|
|
'chart_init': '',
|
||
|
|
'chart_set_labels': 'chart.plot.label = "Data label"',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['data'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
data,
|
||
|
|
colors=None,
|
||
|
|
labels=None,
|
||
|
|
size=None,
|
||
|
|
loc=None,
|
||
|
|
) -> None: # numpydoc ignore=PR01,RT01
|
||
|
|
"""Initialize a new chart containing box plots."""
|
||
|
|
if vtk_version_info >= (9, 2, 0):
|
||
|
|
self.SetAutoSize(False) # We manually set the appropriate size
|
||
|
|
if size is None:
|
||
|
|
size = (1, 1)
|
||
|
|
if loc is None:
|
||
|
|
loc = (0, 0)
|
||
|
|
super().__init__(size, loc)
|
||
|
|
self._plot = BoxPlot(self, data, colors=colors, labels=labels)
|
||
|
|
self.SetPlot(self._plot)
|
||
|
|
self.SetColumnVisibilityAll(True)
|
||
|
|
self.legend_visible = True
|
||
|
|
|
||
|
|
def _render_event(self, *args, **kwargs) -> None:
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
# In older VTK versions, ChartBox fills the entire scene, so
|
||
|
|
# no resizing is needed (nor possible).
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
super()._render_event(*args, **kwargs)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _geometry(self):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
return (0, 0, *self._renderer.GetSize())
|
||
|
|
else:
|
||
|
|
return _Chart._geometry.fget(self) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@_geometry.setter
|
||
|
|
def _geometry(self, value):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
msg = f'Cannot set the geometry of {type(self).__class__}'
|
||
|
|
raise AttributeError(msg)
|
||
|
|
else:
|
||
|
|
_Chart._geometry.fset(self, value) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def plot(self): # numpydoc ignore=RT01
|
||
|
|
"""Return the :class:`~plotting.charts.BoxPlot` instance associated with this chart.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a box plot from a standard Gaussian dataset.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> rng = np.random.default_rng(
|
||
|
|
... 1
|
||
|
|
... ) # Seeded random number generator for data generation
|
||
|
|
>>> chart = pv.ChartBox([rng.normal(size=100)])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the box plot (shift the standard Gaussian distribution).
|
||
|
|
|
||
|
|
>>> chart.plot.update([rng.normal(loc=2, size=100)])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._plot
|
||
|
|
|
||
|
|
@property
|
||
|
|
def size(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart size in normalized coordinates.
|
||
|
|
|
||
|
|
A size of ``(1, 1)`` occupies the whole renderer.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
Customisable ChartBox geometry is only supported in VTK v9.2
|
||
|
|
or newer. For older VTK versions, the size cannot be modified,
|
||
|
|
filling up the entire viewport by default.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a half-sized boxplot chart centered in the middle of the
|
||
|
|
renderer.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartBox([[0, 1, 1, 2, 3, 3, 4]])
|
||
|
|
>>> chart.size = (0.5, 0.5)
|
||
|
|
>>> chart.loc = (0.25, 0.25)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
return (1, 1)
|
||
|
|
else:
|
||
|
|
return _Chart.size.fget(self) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@size.setter
|
||
|
|
def size(self, val):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
msg = (
|
||
|
|
'Cannot set ChartBox geometry, it fills up the entire viewport by default. '
|
||
|
|
'Upgrade to VTK v9.2 or newer.'
|
||
|
|
)
|
||
|
|
raise ValueError(msg)
|
||
|
|
else:
|
||
|
|
_Chart.size.fset(self, val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def loc(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart position in normalized coordinates.
|
||
|
|
|
||
|
|
This denotes the location of the chart's bottom left corner.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
Customisable ChartBox geometry is only supported in VTK v9.2
|
||
|
|
or newer. For older VTK versions, the location cannot be modified,
|
||
|
|
filling up the entire viewport by default.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a half-sized boxplot chart centered in the middle of the
|
||
|
|
renderer.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartBox([[0, 1, 1, 2, 3, 3, 4]])
|
||
|
|
>>> chart.size = (0.5, 0.5)
|
||
|
|
>>> chart.loc = (0.25, 0.25)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
return (0, 0)
|
||
|
|
else:
|
||
|
|
return _Chart.loc.fget(self) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@loc.setter
|
||
|
|
def loc(self, val):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
msg = (
|
||
|
|
'Cannot set ChartBox geometry, it fills up the entire viewport by default. '
|
||
|
|
'Upgrade to VTK v9.2 or newer.'
|
||
|
|
)
|
||
|
|
raise ValueError(msg)
|
||
|
|
else:
|
||
|
|
_Chart.loc.fset(self, val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
|
||
|
|
class PiePlot(_MultiCompPlot, _vtkWrapper, _vtk.vtkPlotPie):
|
||
|
|
"""Class representing a pie plot.
|
||
|
|
|
||
|
|
Users should typically not directly create new plot instances, but
|
||
|
|
use the dedicated :class:`ChartPie` class.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart : ChartPie
|
||
|
|
The chart containing this plot.
|
||
|
|
|
||
|
|
data : array_like
|
||
|
|
Relative size of each pie segment.
|
||
|
|
|
||
|
|
colors : sequence[ColorLike], optional
|
||
|
|
Color of the segments drawn in this plot. Any color parsable
|
||
|
|
by :class:`pyvista.Color` is allowed. If omitted (``None``),
|
||
|
|
the default color scheme is used.
|
||
|
|
|
||
|
|
labels : sequence[str], default: []
|
||
|
|
Label for each pie segment drawn in this plot, as shown in the
|
||
|
|
chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a pie plot showing the usage of tax money.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> x = [128.3, 32.9, 31.8, 29.3, 21.2]
|
||
|
|
>>> l = [
|
||
|
|
... 'Social benefits',
|
||
|
|
... 'Governance',
|
||
|
|
... 'Economic policy',
|
||
|
|
... 'Education',
|
||
|
|
... 'Other',
|
||
|
|
... ]
|
||
|
|
>>> chart = pv.ChartPie(x, labels=l)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'plot_name': 'pie plot',
|
||
|
|
'chart_init': 'pv.ChartPie([4, 3, 2, 1])',
|
||
|
|
'plot_init': 'chart.plot',
|
||
|
|
'multichart_init': 'pv.ChartPie([4, 3, 2, 1])',
|
||
|
|
'multiplot_init': 'chart.plot',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['chart', 'data'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self, chart, data, colors=None, labels=None
|
||
|
|
) -> None:
|
||
|
|
"""Initialize a new pie plot instance."""
|
||
|
|
super().__init__(chart)
|
||
|
|
self._table = pyvista.Table(data)
|
||
|
|
self.SetInputData(self._table)
|
||
|
|
self.SetInputArray(0, self._table.keys()[0])
|
||
|
|
self.update(data)
|
||
|
|
|
||
|
|
self.labels = labels
|
||
|
|
|
||
|
|
self.SetColorSeries(self._color_series)
|
||
|
|
self.colors = colors
|
||
|
|
|
||
|
|
@property
|
||
|
|
def data(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the sizes of the drawn segments.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a pie plot and display the sizes.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartPie([1, 2, 3])
|
||
|
|
>>> chart.plot.data
|
||
|
|
pyvista_ndarray([1, 2, 3])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._table[0]
|
||
|
|
|
||
|
|
def update(self, data) -> None:
|
||
|
|
"""Update the size of the pie segments.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
data : array_like
|
||
|
|
The new relative size of each pie segment.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a pie plot with segments of increasing size.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartPie([1, 2, 3, 4, 5])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the pie plot (segments of equal size).
|
||
|
|
|
||
|
|
>>> chart.plot.update([1, 1, 1, 1, 1])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
self._table.update(data)
|
||
|
|
|
||
|
|
|
||
|
|
class ChartPie(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _Chart, _vtk.vtkChartPie):
|
||
|
|
"""Dedicated chart for drawing pie plots.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
data : array_like
|
||
|
|
Relative size of each pie segment.
|
||
|
|
|
||
|
|
colors : sequence[ColorLike], optional
|
||
|
|
Color used for each pie segment drawn in this plot. If
|
||
|
|
omitted (``None``), the default color scheme is used.
|
||
|
|
|
||
|
|
labels : sequence[str], default: []
|
||
|
|
Label for each pie segment drawn in this plot, as shown in the
|
||
|
|
chart's legend.
|
||
|
|
|
||
|
|
size : sequence[float], optional
|
||
|
|
Size of the chart in normalized coordinates. A size of ``(0,
|
||
|
|
0)`` is invisible, a size of ``(1, 1)`` occupies the whole
|
||
|
|
renderer's width and height.
|
||
|
|
|
||
|
|
loc : sequence[float], optional
|
||
|
|
Location of the chart (its bottom left corner) in normalized
|
||
|
|
coordinates. A location of ``(0, 0)`` corresponds to the
|
||
|
|
renderer's bottom left corner, a location of ``(1, 1)``
|
||
|
|
corresponds to the renderer's top right corner.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a pie plot showing the usage of tax money.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> x = [128.3, 32.9, 31.8, 29.3, 21.2]
|
||
|
|
>>> l = [
|
||
|
|
... 'Social benefits',
|
||
|
|
... 'Governance',
|
||
|
|
... 'Economic policy',
|
||
|
|
... 'Education',
|
||
|
|
... 'Other',
|
||
|
|
... ]
|
||
|
|
>>> chart = pv.ChartPie(x, labels=l)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'chart_name': 'pie chart',
|
||
|
|
'chart_args': '[5, 4, 3, 2, 1]',
|
||
|
|
'chart_init': '',
|
||
|
|
'chart_set_labels': 'chart.plot.labels = ["A", "B", "C", "D", "E"]',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['data'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
data,
|
||
|
|
colors=None,
|
||
|
|
labels=None,
|
||
|
|
size=None,
|
||
|
|
loc=None,
|
||
|
|
) -> None: # numpydoc ignore=PR01,RT01
|
||
|
|
"""Initialize a new chart containing a pie plot."""
|
||
|
|
if vtk_version_info >= (9, 2, 0):
|
||
|
|
self.SetAutoSize(False) # We manually set the appropriate size
|
||
|
|
if size is None:
|
||
|
|
size = (1, 1)
|
||
|
|
if loc is None:
|
||
|
|
loc = (0, 0)
|
||
|
|
super().__init__(size, loc)
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
# SetPlot method is not available for older VTK versions,
|
||
|
|
# so fallback to using a wrapper object.
|
||
|
|
self.AddPlot(0)
|
||
|
|
self._plot = PiePlot(self, data, colors=colors, labels=labels, _wrap=self.GetPlot(0)) # type: ignore[call-arg]
|
||
|
|
else:
|
||
|
|
self._plot = PiePlot(self, data, colors=colors, labels=labels)
|
||
|
|
self.SetPlot(self._plot)
|
||
|
|
self.legend_visible = True
|
||
|
|
|
||
|
|
def _render_event(self, *args, **kwargs) -> None:
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
# In older VTK versions, ChartPie fills the entire scene, so
|
||
|
|
# no resizing is needed (nor possible).
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
super()._render_event(*args, **kwargs)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _geometry(self):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
return (0, 0, *self._renderer.GetSize())
|
||
|
|
else:
|
||
|
|
return _Chart._geometry.fget(self) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@_geometry.setter
|
||
|
|
def _geometry(self, value):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
msg = f'Cannot set the geometry of {type(self).__class__}'
|
||
|
|
raise AttributeError(msg)
|
||
|
|
else:
|
||
|
|
_Chart._geometry.fset(self, value) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def plot(self): # numpydoc ignore=RT01
|
||
|
|
"""Return the :class:`~plotting.charts.PiePlot` instance associated with this chart.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a pie plot with segments of increasing size.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartPie([1, 2, 3, 4, 5])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Update the pie plot (segments of equal size).
|
||
|
|
|
||
|
|
>>> chart.plot.update([1, 1, 1, 1, 1])
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._plot
|
||
|
|
|
||
|
|
@property
|
||
|
|
def size(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart size in normalized coordinates.
|
||
|
|
|
||
|
|
A size of ``(1, 1)`` occupies the whole renderer.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
Customisable ChartPie geometry is only supported in VTK v9.2
|
||
|
|
or newer. For older VTK versions, the size cannot be modified,
|
||
|
|
filling up the entire viewport by default.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a half-sized pie chart centered in the middle of the
|
||
|
|
renderer.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartPie([5, 4, 3, 2, 1])
|
||
|
|
>>> chart.size = (0.5, 0.5)
|
||
|
|
>>> chart.loc = (0.25, 0.25)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
return (1, 1)
|
||
|
|
else:
|
||
|
|
return _Chart.size.fget(self) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@size.setter
|
||
|
|
def size(self, val):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
msg = (
|
||
|
|
'Cannot set ChartPie geometry, it fills up the entire viewport by default. '
|
||
|
|
'Upgrade to VTK v9.2 or newer.'
|
||
|
|
)
|
||
|
|
raise ValueError(msg)
|
||
|
|
else:
|
||
|
|
_Chart.size.fset(self, val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def loc(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart position in normalized coordinates.
|
||
|
|
|
||
|
|
This denotes the location of the chart's bottom left corner.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
Customisable ChartPie geometry is only supported in VTK v9.2
|
||
|
|
or newer. For older VTK versions, the location cannot be modified,
|
||
|
|
filling up the entire viewport by default.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a half-sized pie chart centered in the middle of the
|
||
|
|
renderer.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> chart = pv.ChartPie([5, 4, 3, 2, 1])
|
||
|
|
>>> chart.size = (0.5, 0.5)
|
||
|
|
>>> chart.loc = (0.25, 0.25)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
return (0, 0)
|
||
|
|
else:
|
||
|
|
return _Chart.loc.fget(self) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
@loc.setter
|
||
|
|
def loc(self, val):
|
||
|
|
if vtk_version_info < (9, 2, 0): # pragma: no cover
|
||
|
|
msg = (
|
||
|
|
'Cannot set ChartPie geometry, it fills up the entire viewport by default. '
|
||
|
|
'Upgrade to VTK v9.2 or newer.'
|
||
|
|
)
|
||
|
|
raise ValueError(msg)
|
||
|
|
else:
|
||
|
|
_Chart.loc.fset(self, val) # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
|
||
|
|
class ChartMPL(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _Chart, _vtk.vtkImageItem):
|
||
|
|
"""Create new chart from an existing matplotlib figure.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
figure : matplotlib.figure.Figure, optional
|
||
|
|
The matplotlib figure to draw. If no figure is
|
||
|
|
provided ( ``None`` ), a new figure is created.
|
||
|
|
|
||
|
|
size : sequence[float], default: (1, 1)
|
||
|
|
Size of the chart in normalized coordinates. A size of ``(0,
|
||
|
|
0)`` is invisible, a size of ``(1, 1)`` occupies the whole
|
||
|
|
renderer's width and height.
|
||
|
|
|
||
|
|
loc : sequence[float], default: (0, 0)
|
||
|
|
Location of the chart (its bottom left corner) in normalized
|
||
|
|
coordinates. A location of ``(0, 0)`` corresponds to the
|
||
|
|
renderer's bottom left corner, a location of ``(1, 1)``
|
||
|
|
corresponds to the renderer's top right corner.
|
||
|
|
|
||
|
|
redraw_on_render : bool, default: True
|
||
|
|
Flag indicating whether the chart should be redrawn when
|
||
|
|
the plotter is rendered. For static charts, setting this
|
||
|
|
to ``False`` can improve performance.
|
||
|
|
|
||
|
|
See Also
|
||
|
|
--------
|
||
|
|
:ref:`chart_overlays_example`
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Plot streamlines of a vector field with varying colors (based on `this example <https://matplotlib.org/stable/gallery/images_contours_and_fields/plot_streamplot.html>`_).
|
||
|
|
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> import matplotlib.pyplot as plt
|
||
|
|
|
||
|
|
>>> w = 3
|
||
|
|
>>> Y, X = np.mgrid[-w:w:100j, -w:w:100j]
|
||
|
|
>>> U = -1 - X**2 + Y
|
||
|
|
>>> V = 1 + X - Y**2
|
||
|
|
>>> speed = np.sqrt(U**2 + V**2)
|
||
|
|
|
||
|
|
>>> f, ax = plt.subplots()
|
||
|
|
>>> strm = ax.streamplot(X, Y, U, V, color=U, linewidth=2, cmap='autumn')
|
||
|
|
>>> _ = f.colorbar(strm.lines)
|
||
|
|
>>> _ = ax.set_title('Streamplot with varying Color')
|
||
|
|
>>> plt.tight_layout()
|
||
|
|
|
||
|
|
>>> chart = pv.ChartMPL(f)
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
_DOC_SUBS = { # noqa: RUF012
|
||
|
|
'chart_name': 'matplotlib chart',
|
||
|
|
'chart_args': '',
|
||
|
|
'chart_init': """
|
||
|
|
>>> plots = chart.figure.axes[0].plot([0, 1, 2], [2, 1, 3])""",
|
||
|
|
'chart_set_labels': 'plots[0].label = "My awesome plot"',
|
||
|
|
}
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['figure'])
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
figure=None,
|
||
|
|
size=(1, 1),
|
||
|
|
loc=(0, 0),
|
||
|
|
redraw_on_render: bool = True, # noqa: FBT001, FBT002
|
||
|
|
) -> None: # numpydoc ignore=PR01,RT01
|
||
|
|
"""Initialize chart."""
|
||
|
|
super().__init__(size, loc)
|
||
|
|
if figure is None:
|
||
|
|
figure, _ = plt.subplots()
|
||
|
|
self._fig = figure
|
||
|
|
self._canvas = FigureCanvasAgg(
|
||
|
|
self._fig,
|
||
|
|
) # Switch backends and store reference to figure's canvas
|
||
|
|
# Make figure and axes fully transparent, as the background is already dealt with
|
||
|
|
# by self._background.
|
||
|
|
self._fig.patch.set_alpha(0)
|
||
|
|
for ax in self._fig.axes:
|
||
|
|
ax.patch.set_alpha(0)
|
||
|
|
self._canvas.mpl_connect('draw_event', self._redraw) # Attach 'draw_event' callback
|
||
|
|
self._redraw_on_render = redraw_on_render
|
||
|
|
|
||
|
|
self._redraw()
|
||
|
|
|
||
|
|
# Close the underlying matplotlib figure when creating the sphinx gallery.
|
||
|
|
# This prevents the charts from being drawn twice in example scripts:
|
||
|
|
# once as a pyvista plot (fetched by the 'pyvista' scraper) and once as a
|
||
|
|
# matplotlib figure (fetched by the 'matplotlib' scraper).
|
||
|
|
# See #1999 and #2031.
|
||
|
|
if pyvista.BUILDING_GALLERY: # pragma: no cover
|
||
|
|
plt.close(self._fig)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def figure(self): # numpydoc ignore=RT01
|
||
|
|
"""Retrieve the matplotlib figure associated with this chart.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a matplotlib chart from an existing figure.
|
||
|
|
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import matplotlib.pyplot as plt
|
||
|
|
>>> f, ax = plt.subplots()
|
||
|
|
>>> _ = ax.plot([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart = pv.ChartMPL(f)
|
||
|
|
>>> chart.figure is f
|
||
|
|
True
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._fig
|
||
|
|
|
||
|
|
@property
|
||
|
|
def redraw_on_render(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's redraw-on-render behavior.
|
||
|
|
|
||
|
|
Notes
|
||
|
|
-----
|
||
|
|
When disabled, the chart will only be redrawn when the
|
||
|
|
Plotter window is resized or the matplotlib figure is
|
||
|
|
manually redrawn using ``fig.canvas.draw()``.
|
||
|
|
When enabled, the chart will also be automatically
|
||
|
|
redrawn whenever the Plotter is rendered using
|
||
|
|
``plotter.render()``.
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._redraw_on_render
|
||
|
|
|
||
|
|
@redraw_on_render.setter
|
||
|
|
def redraw_on_render(self, val) -> None:
|
||
|
|
self._redraw_on_render = bool(val)
|
||
|
|
|
||
|
|
def _resize(self):
|
||
|
|
r_w, r_h = self._renderer.GetSize()
|
||
|
|
c_w, c_h = (int(s) for s in self._canvas.get_width_height())
|
||
|
|
# Calculate target size from specified normalized width and height
|
||
|
|
# and the renderer's current size
|
||
|
|
t_w = int(self._size[0] * r_w)
|
||
|
|
t_h = int(self._size[1] * r_h)
|
||
|
|
resize = c_w != t_w or c_h != t_h
|
||
|
|
if resize:
|
||
|
|
# Mismatch between canvas size and target size, so resize figure:
|
||
|
|
f_w = t_w / self._fig.dpi
|
||
|
|
f_h = t_h / self._fig.dpi
|
||
|
|
self._fig.set_size_inches(f_w, f_h)
|
||
|
|
self.position = (int(self._loc[0] * r_w), int(self._loc[1] * r_h))
|
||
|
|
return resize
|
||
|
|
|
||
|
|
def _redraw(self, event=None) -> None:
|
||
|
|
"""Redraw the chart."""
|
||
|
|
if event is None:
|
||
|
|
# Manual call, so make sure canvas is redrawn first (which will callback to _redraw
|
||
|
|
# with a proper event defined)
|
||
|
|
self._canvas.draw()
|
||
|
|
else:
|
||
|
|
# Called from draw_event callback
|
||
|
|
img = np.frombuffer(
|
||
|
|
self._canvas.buffer_rgba(),
|
||
|
|
dtype=np.uint8,
|
||
|
|
) # Store figure data in numpy array
|
||
|
|
w, h = self._canvas.get_width_height()
|
||
|
|
img_arr = img.reshape([h, w, 4])
|
||
|
|
img_data = pyvista.Texture(img_arr).to_image() # type: ignore[abstract] # Convert to vtkImageData
|
||
|
|
self.SetImage(img_data)
|
||
|
|
|
||
|
|
def _render_event(self, *_, plotter_render: bool = False, **__) -> None:
|
||
|
|
# Redraw figure when geometry has changed (self._resize call
|
||
|
|
# already updated figure dimensions in that case) OR the
|
||
|
|
# plotter's render method was called and redraw_on_render is
|
||
|
|
# enabled.
|
||
|
|
if (plotter_render and self.redraw_on_render) or (not plotter_render and self._resize()):
|
||
|
|
self._redraw()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _geometry(self):
|
||
|
|
r_w, r_h = self._renderer.GetSize()
|
||
|
|
t_w = self._size[0] * r_w
|
||
|
|
t_h = self._size[1] * r_h
|
||
|
|
return (*self.position, t_w, t_h)
|
||
|
|
|
||
|
|
@_geometry.setter
|
||
|
|
def _geometry(self, _):
|
||
|
|
msg = f'Cannot set the geometry of {type(self).__class__}'
|
||
|
|
raise AttributeError(msg)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def position(self): # numpydoc ignore=RT01
|
||
|
|
"""Chart position w.r.t the bottom left corner (in pixels)."""
|
||
|
|
return self.GetPosition()
|
||
|
|
|
||
|
|
@position.setter
|
||
|
|
def position(self, val):
|
||
|
|
if len(val) != 2:
|
||
|
|
msg = f'Invalid position {val}, must be length 2.'
|
||
|
|
raise ValueError(msg)
|
||
|
|
self.SetPosition(*val)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def title(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the chart's title.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a matplotlib chart with title 'My Chart'.
|
||
|
|
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import matplotlib.pyplot as plt
|
||
|
|
>>> f, ax = plt.subplots()
|
||
|
|
>>> _ = ax.plot([0, 1, 2], [2, 1, 3])
|
||
|
|
>>> chart = pv.ChartMPL(f)
|
||
|
|
>>> chart.title = 'My Chart'
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self._fig._suptitle.get_text()
|
||
|
|
|
||
|
|
@title.setter
|
||
|
|
def title(self, val) -> None:
|
||
|
|
self._fig.suptitle(val)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def legend_visible(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the visibility of the chart's legend.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a matplotlib chart with custom labels and show the legend.
|
||
|
|
|
||
|
|
.. pyvista-plot::
|
||
|
|
:force_static:
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> import matplotlib.pyplot as plt
|
||
|
|
>>> f, ax = plt.subplots()
|
||
|
|
>>> _ = ax.plot([0, 1, 2], [2, 1, 3], label='Line')
|
||
|
|
>>> _ = ax.scatter([0, 1, 2], [3, 2, 1], label='Points')
|
||
|
|
>>> chart = pv.ChartMPL(f)
|
||
|
|
>>> chart.legend_visible = True
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
Hide the legend.
|
||
|
|
|
||
|
|
>>> chart.legend_visible = False
|
||
|
|
>>> chart.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
legend = self._fig.axes[0].get_legend()
|
||
|
|
return False if legend is None else legend.get_visible()
|
||
|
|
|
||
|
|
@legend_visible.setter
|
||
|
|
def legend_visible(self, val) -> None:
|
||
|
|
legend = self._fig.axes[0].get_legend()
|
||
|
|
if legend is None:
|
||
|
|
legend = self._fig.axes[0].legend()
|
||
|
|
legend.set_visible(val)
|
||
|
|
|
||
|
|
|
||
|
|
class Charts(_NoNewAttrMixin):
|
||
|
|
"""Collection of charts for a renderer.
|
||
|
|
|
||
|
|
Users should typically not directly create new instances of this
|
||
|
|
class, but use the dedicated ``Plotter.add_chart`` method.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
renderer : pyvista.Renderer
|
||
|
|
The renderer to which the charts should be added.
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, renderer) -> None:
|
||
|
|
"""Create a new collection of charts for the given renderer."""
|
||
|
|
self._charts: list[_Chart] = []
|
||
|
|
|
||
|
|
# Postpone creation of scene and actor objects until they are
|
||
|
|
# needed.
|
||
|
|
self._scene: _vtk.vtkContextScene | None = None
|
||
|
|
self._actor: _vtk.vtkContextActor | None = None
|
||
|
|
|
||
|
|
# a weakref.proxy would be nice here, but that doesn't play
|
||
|
|
# nicely with SetRenderer, so instead we'll use a weak reference
|
||
|
|
# plus a property to call it
|
||
|
|
self.__renderer = weakref.ref(renderer)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def _renderer(self):
|
||
|
|
"""Return the weakly dereferenced renderer, maybe None."""
|
||
|
|
return self.__renderer()
|
||
|
|
|
||
|
|
def _setup_scene(self) -> None:
|
||
|
|
"""Set up a new context scene and actor for these charts."""
|
||
|
|
self._scene = _vtk.vtkContextScene()
|
||
|
|
self._actor = _vtk.vtkContextActor()
|
||
|
|
|
||
|
|
self._actor.SetScene(self._scene)
|
||
|
|
self._renderer.AddActor(self._actor)
|
||
|
|
self._scene.SetRenderer(self._renderer)
|
||
|
|
|
||
|
|
def deep_clean(self) -> None:
|
||
|
|
"""Remove all references to the chart objects and internal objects."""
|
||
|
|
if self._scene is not None:
|
||
|
|
charts = [*self._charts] # Make a copy, as this list will be modified by remove_chart
|
||
|
|
for chart in charts:
|
||
|
|
self.remove_chart(chart)
|
||
|
|
if self._renderer is not None:
|
||
|
|
self._renderer.RemoveActor(self._actor)
|
||
|
|
self._scene = None
|
||
|
|
self._actor = None
|
||
|
|
|
||
|
|
def add_chart(self, *charts) -> None:
|
||
|
|
"""Add charts to the collection.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
*charts : Chart2D | Chart3D
|
||
|
|
One or more chart objects to be added to the collection.
|
||
|
|
|
||
|
|
See Also
|
||
|
|
--------
|
||
|
|
:ref:`chart_overlays_example`
|
||
|
|
|
||
|
|
"""
|
||
|
|
if self._scene is None:
|
||
|
|
self._setup_scene()
|
||
|
|
for chart in charts:
|
||
|
|
self._charts.append(chart)
|
||
|
|
if chart._background is not None:
|
||
|
|
self._scene.AddItem(chart._background) # type: ignore[union-attr]
|
||
|
|
self._scene.AddItem(chart) # type: ignore[union-attr]
|
||
|
|
chart._interactive = False # Charts are not interactive by default
|
||
|
|
|
||
|
|
@_deprecate_positional_args(allowed=['interactive'])
|
||
|
|
def set_interaction(self, interactive, toggle: bool = False): # noqa: FBT001, FBT002
|
||
|
|
"""Set or toggle interaction with charts for this renderer.
|
||
|
|
|
||
|
|
Interaction with other charts in this renderer is disabled when ``toggle``
|
||
|
|
is ``False``.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
interactive : bool | Chart | int | list[Chart] | list[int]
|
||
|
|
Following parameter values are accepted:
|
||
|
|
|
||
|
|
* A boolean to enable (``True``) or disable (``False``) interaction
|
||
|
|
with all charts.
|
||
|
|
* The chart or its index to enable interaction with. Interaction
|
||
|
|
with multiple charts can be enabled by passing a list of charts
|
||
|
|
or indices.
|
||
|
|
|
||
|
|
toggle : bool, default: False
|
||
|
|
Instead of enabling interaction with the provided chart(s), interaction
|
||
|
|
with the provided chart(s) is toggled. Only applicable when ``interactive``
|
||
|
|
is not a boolean.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
list[Chart]
|
||
|
|
The list of all interactive charts for this renderer.
|
||
|
|
|
||
|
|
"""
|
||
|
|
if isinstance(interactive, bool):
|
||
|
|
# Disable toggle and convert to list of charts
|
||
|
|
toggle = False
|
||
|
|
interactive = self._charts if interactive else []
|
||
|
|
if not isinstance(interactive, list):
|
||
|
|
# Convert single chart parameter to list
|
||
|
|
interactive = [interactive]
|
||
|
|
# Convert to list of Charts
|
||
|
|
charts = [
|
||
|
|
self._charts[coi] if isinstance(coi, int) and 0 <= coi < len(self) else coi
|
||
|
|
for coi in interactive
|
||
|
|
]
|
||
|
|
interactive_charts = []
|
||
|
|
|
||
|
|
for chart in self._charts:
|
||
|
|
# Determine whether to enable interaction with the current chart.
|
||
|
|
if toggle:
|
||
|
|
enable = not chart._interactive if chart in charts else chart._interactive
|
||
|
|
else:
|
||
|
|
enable = chart in charts
|
||
|
|
|
||
|
|
chart._interactive = enable
|
||
|
|
if enable:
|
||
|
|
interactive_charts.append(chart)
|
||
|
|
|
||
|
|
return interactive_charts
|
||
|
|
|
||
|
|
def remove_chart(self, chart_or_index):
|
||
|
|
"""Remove a chart from the collection.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
chart_or_index : int or Chart
|
||
|
|
The index or the chart object to be removed from the collection.
|
||
|
|
|
||
|
|
Raises
|
||
|
|
------
|
||
|
|
ValueError
|
||
|
|
If the specified chart index is not present in the charts collection.
|
||
|
|
|
||
|
|
"""
|
||
|
|
chart: _Chart = (
|
||
|
|
self._charts[chart_or_index] if isinstance(chart_or_index, int) else chart_or_index
|
||
|
|
)
|
||
|
|
if chart not in self._charts: # pragma: no cover
|
||
|
|
msg = 'chart_index not present in charts collection.'
|
||
|
|
raise ValueError(msg)
|
||
|
|
self._charts.remove(chart)
|
||
|
|
if self._scene is not None:
|
||
|
|
self._scene.RemoveItem(chart) # type: ignore[call-overload]
|
||
|
|
if chart._background is not None:
|
||
|
|
self._scene.RemoveItem(chart._background) # type: ignore[union-attr]
|
||
|
|
|
||
|
|
def get_charts_by_pos(self, pos):
|
||
|
|
"""Retrieve visible charts indicated by the given mouse position.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
pos : sequence[float]
|
||
|
|
Tuple containing the mouse position.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
list of Chart
|
||
|
|
Visible charts indicated by the given mouse position.
|
||
|
|
|
||
|
|
"""
|
||
|
|
return [chart for chart in self._charts if chart.visible and chart._is_within(pos)]
|
||
|
|
|
||
|
|
def __getitem__(self, index) -> Chart:
|
||
|
|
"""Return a chart based on an index."""
|
||
|
|
return self._charts[index]
|
||
|
|
|
||
|
|
def __len__(self):
|
||
|
|
"""Return number of charts."""
|
||
|
|
return len(self._charts)
|
||
|
|
|
||
|
|
def __iter__(self):
|
||
|
|
"""Return an iterable of charts."""
|
||
|
|
yield from self._charts
|
||
|
|
|
||
|
|
def __del__(self) -> None:
|
||
|
|
"""Clean up before being destroyed."""
|
||
|
|
self.deep_clean()
|