Source code for squap.widgets.plot_widget_3D

from pyqtgraph.opengl import GLViewWidget, GLGridItem
from PySide6.QtWidgets import QSizePolicy
from PySide6.QtGui import QVector3D

import numpy as np
from typing import Iterable, Optional

from ..helper_funcs import get_new_kwargs, ColorsType
from .curves_3D import ScatterCurve3D


[docs] class SubplotWidget3D(GLViewWidget): """Widget for 3D plots. Obtain from :func:`squap.make_3D`""" def __init__(self, row: int, col: int, **kwargs): super().__init__(**kwargs) self.row = row # for merging subplots self.col = col self.curves = [] # for clearing curves self.zoom_rate = 1.003 self.camera_pos = np.array([.0, .0, .0]) # change the size policy, so it is the same as a regular plot widget size_policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setSizePolicy(size_policy) def add_curve(self, curve): self.addItem(curve) def wheelEvent(self, ev): # changes default to changing distance instead of fov angledelta = ev.angleDelta() delta = -angledelta.x()-angledelta.y() self.opts["distance"] *= self.zoom_rate**delta self.update()
[docs] def set_camera(self, x_offset: Optional[float] = None, y_offset: Optional[float] = None, z_offset: Optional[float] = None, distance: Optional[float] = None, azimuth: Optional[float] = None, elevation: Optional[float] = None, fov: Optional[float] = None): """ Sets the camera offset, distance, rotation and fov using the given arguments. ``distance``, ``azimuth`` and ``elevation`` act like polar coordinates around the origin, and ``x_offset``, ``y_offset`` and ``z_offset`` determine the position of this origin. Use :func:`squap.align_camera` to experiment with these values and find the correct ones. Args: x_offset (float, optional): New x offset of the origin. y_offset (float, optional): New y offset of the origin. z_offset (float, optional): New z offset of the origin. distance (float, optional): New distance between the camera position and the origin. azimuth (float, optional): Angle of rotation horizontally around the origin. elevation (float, optional): Angle of rotation vertically about the origin. fov (float, optional): Field of view. """ cam_params_kwargs = {} if distance is not None: cam_params_kwargs["distance"] = distance if azimuth is not None: cam_params_kwargs["azimuth"] = azimuth if elevation is not None: cam_params_kwargs["elevation"] = elevation if fov is not None: cam_params_kwargs["fov"] = fov self.setCameraParams(**cam_params_kwargs) if x_offset is not None or y_offset is not None or z_offset is not None: if x_offset is not None: self.camera_pos[0] = x_offset if y_offset is not None: self.camera_pos[1] = y_offset if z_offset is not None: self.camera_pos[2] = z_offset self.setCameraPosition( QVector3D(*self.camera_pos) )
[docs] def set_zoom_rate(self, new_rate: float = 1.003): """Sets the rate at which you zoom when the plot is zoomed after it has been created. Starts off at ``1.003``. """ self.zoom_rate = new_rate
[docs] def add_grid(self, diagonal: tuple[float], spacing: float = 1) -> GLGridItem: """ Adds a grid that lies on or parallel to one of the planes. Args: diagonal (tuple of float): A tuple of two coordinates that span the diagonal of a rectangle that lies on or parallel to the xy, yz, or zx plane. spacing (float): Spacing between the grid lines. Defaults to 1. Returns: :class:`~pyqtgraph.opengl.GLGridItem`: The created grid. """ diagonal = np.array(diagonal) size_arr = (np.abs(diagonal[0, :] - diagonal[1, :])) # difference between diagonals size = [i for i in size_arr if i != 0] # size without 0's. if len(size) != 2: if len(size) == 3: raise ValueError( f"The given diagonal is not the diagonal of a rectangle that lies parallel to the xy, " f"yz or zx plane. Try using `add_complex_grid`.") else: raise ValueError(f"The given diagonal lies on a straight line, parrallel to the axis.") grid = GLGridItem() grid.setSize(*size) if size_arr[0] == 0: # checks in which plane it lies grid.rotate(90, 0, 0, 1) # this one needs to be rotated 90 degrees in the xy plane first grid.rotate(90, 0, 1, 0) if size_arr[1] == 0: grid.rotate(90, 1, 0, 0) bot_corner = np.min(diagonal, axis=0) # bottom corner of the rectangle spanned by the diagonals. grid.translate(*(size_arr / 2 + bot_corner)) self.curves.append(grid) self.addItem(grid) grid.remove = lambda: self.removeItem(grid) grid.setSpacing(x=spacing, y=spacing, z=spacing) return grid
[docs] def add_grids(self, size: tuple[float], spacing: float = 1) -> list[GLGridItem]: # maybe change to grids with max_x, max_y and max_z """ Adds square grids on the xy, yz and zx planes, spanning from ``size[0]`` to ``size[1]``. Args: size (tuple of float): the size of each grid. spacing (float): Spacing between the grid lines. Defaults to 1. Returns: list of :class:`~pyqtgraph.opengl.GLGridItem`: The created grids. """ grids = [ self.add_grid(((size[0], size[0], 0), (size[1], size[1], 0))), self.add_grid(((size[0], 0, size[0]), (size[1], 0, size[1]))), self.add_grid(((0, size[0], size[0]), (0, size[1], size[1]))) ] for grid in grids: grid.setSpacing(x=spacing, y=spacing, z=spacing) return grids
[docs] def scatter(self, x: Optional[Iterable] = None, y: Optional[Iterable] = None, z: Optional[Iterable] = None, pos: Optional[np.ndarray] = None, color: ColorsType = "y", size: int | Iterable[int] = 4, pixel_mode: bool = True, **kwargs): """ Creates a new scatter plot item. Use either ``x``, ``y`` and ``z`` or ``pos`` to set the coordinates of the points. Args: x (:class:`np.ndarray <numpy.ndarray>`, optional): New x-locations of each point. Defaults to ``None``, which means the data will be initialised with ``pos``, or it will be initialised later. y (:class:`np.ndarray <numpy.ndarray>`, optional): New y-locations of each point. Defaults to ``None``, which means the data will be initialised with ``pos``, or it will be initialised later. z (:class:`np.ndarray <numpy.ndarray>`, optional): New z-locations of each point. Defaults to ``None``, which means the data will be initialised with ``pos``, or it will be initialised later. pos (:class:`np.ndarray <numpy.ndarray>`, optional): Shape ``(N, 3)`` (:class:`array <numpy.ndarray>`) of floats specifying point locations. Can be used instead of ``x``, ``y`` and ``z``. color (:ref:`ColorsType`): Changes the color of the points. See :ref:`ColorsType` for allowed values. size (int or list of int): The size of the scatter plot points. Also accepted as ``s``. Default is ``4``. pixel_mode (bool): Whether to fix the size of each point. If ``True``, size is specified in pixels. If ``False``, size is specified in data coordinates. Defaults to ``True``. """ new_kwargs = get_new_kwargs(locals(), none_kwargs=["x", "y", "z", "pos"], exclude_args=["self", "kwargs"]) curve = ScatterCurve3D() curve.set_data(**new_kwargs, **kwargs) self.add_curve(curve) return curve