Source code for qudi.util.widgets.plotting.marker

# -*- coding: utf-8 -*-

"""
This file contains customized pyqtgraph graphics items to be used as data marker in 1D and 2D plots.
This is an attempt to provide a somewhat unified interface to these markers contrary to the
heterogeneous interfaces of pyqtgraph items.

Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this
distribution and on <https://github.com/Ulm-IQO/qudi-core/>

This file is part of qudi.

Qudi is free software: you can redistribute it and/or modify it under the terms of
the GNU Lesser General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.

Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with qudi.
If not, see <https://www.gnu.org/licenses/>.
"""

__all__ = ['InfiniteCrosshair', 'InfiniteLine', 'LinearRegion', 'Rectangle',
           'InfiniteCrosshairRectangle']

from math import isinf
from typing import Union, Tuple, Optional, List, Sequence, Any

from PySide2 import QtCore
from pyqtgraph import ViewBox
from pyqtgraph import ROI as _ROI
from pyqtgraph import LinearRegionItem as _LinearRegionItem
from pyqtgraph import InfiniteLine as _InfiniteLine
from qudi.util.widgets.plotting.roi import RectangleROI as _RectangleROI


[docs] class InfiniteCrosshair(QtCore.QObject): """ Represents a crosshair (two perpendicular infinite lines) """ _default_pen = {'color': '#00ff00', 'width': 1} _default_hover_pen = {'color': '#ffff00', 'width': 1} sigPositionChanged = QtCore.Signal(tuple) # current_pos # start_pos, current_pos, is_start, is_finished sigPositionDragged = QtCore.Signal(tuple, tuple, bool, bool)
[docs] def __init__(self, viewbox: ViewBox, position: Optional[Tuple[float, float]] = (0, 0), bounds: Optional[Sequence[Tuple[Union[None, float], Union[None, float]]]] = None, movable: Optional[bool] = True, pen: Optional[Any] = None, hover_pen: Optional[Any] = None, ) -> None: super().__init__(parent=viewbox) if position is None: position = (0, 0) if movable is None: movable = True if pen is None: pen = self._default_pen if hover_pen is None: hover_pen = self._default_hover_pen self._bounds = self._normalize_bounds(bounds) self._z_value = None self._is_dragged = False self.vline = _InfiniteLine(pos=position[0], bounds=self._bounds[0], angle=90, movable=movable, pen=pen, hoverPen=hover_pen) self.hline = _InfiniteLine(pos=position[1], bounds=self._bounds[1], angle=0, movable=movable, pen=pen, hoverPen=hover_pen) self.vline.sigDragged.connect(self._line_dragged) self.vline.sigPositionChanged.connect(self._line_changed) self.vline.sigPositionChangeFinished.connect(self._line_drag_finished) self.hline.sigDragged.connect(self._line_dragged) self.hline.sigPositionChanged.connect(self._line_changed) self.hline.sigPositionChangeFinished.connect(self._line_drag_finished) self.show()
@property def movable(self) -> bool: return bool(self.vline.movable) def set_movable(self, movable: bool) -> None: """ (Un-)Set the crosshair movable (draggable by mouse cursor) """ self.vline.setMovable(movable) self.hline.setMovable(movable) @property def z_value(self) -> Union[None, int]: return self._z_value def set_z_value(self, value: int) -> None: self.vline.setZValue(value) self.hline.setZValue(value) self._z_value = value @property def position(self) -> Tuple[float, float]: return self.vline.value(), self.hline.value() def set_position(self, pos: Tuple[float, float]) -> None: self.vline.blockSignals(True) self.hline.blockSignals(True) try: self.vline.setPos(pos[0]) self.hline.setPos(pos[1]) finally: self.vline.blockSignals(False) self.hline.blockSignals(False) self.sigPositionChanged.emit(self.position) @property def bounds(self) -> List[Tuple[Union[None, float], Union[None, float]]]: return self._bounds.copy() def set_bounds(self, bounds: Union[None, Sequence[Tuple[Union[None, float], Union[None, float]]]] ) -> None: """ Sets a range boundary for the crosshair position """ self._bounds = self._normalize_bounds(bounds) self.vline.setBounds(self._bounds[0]) self.hline.setBounds(self._bounds[1]) def show(self): view = self.parent() if self.vline not in view.addedItems: view.addItem(self.vline) if self._z_value is not None: self.vline.setZValue(self._z_value) if self.hline not in view.addedItems: view.addItem(self.hline) if self._z_value is not None: self.hline.setZValue(self._z_value) def hide(self): view = self.parent() if self.vline in view.addedItems: view.removeItem(self.vline) if self.hline in view.addedItems: view.removeItem(self.hline) def set_pen(self, pen: Any) -> None: """ Sets the pen to be used for drawing the crosshair lines. Given parameter must be compatible with pyqtgraph.mkPen() """ self.vline.setPen(pen) self.hline.setPen(pen) def set_hover_pen(self, pen: Any) -> None: """ Sets the pen to be used for drawing the crosshair lines when the mouse cursor is hovering over them. Given parameter must be compatible with pyqtgraph.mkPen() """ self.vline.setHoverPen(pen) self.hline.setHoverPen(pen) @staticmethod def _normalize_bounds( bounds: Union[None, Sequence[Tuple[Union[None, float], Union[None, float]]]] ) -> List[Tuple[Union[None, float], Union[None, float]]]: if bounds is None: bounds = [(None, None), (None, None)] else: bounds = [list(span) for span in bounds] # Replace inf values by None for span in bounds: for ii, val in enumerate(span): try: if isinf(val): span[ii] = None except TypeError: pass # Sort spans in ascending order try: bounds[0] = tuple(sorted(bounds[0])) except TypeError: bounds[0] = tuple(bounds[0]) try: bounds[1] = tuple(sorted(bounds[1])) except TypeError: bounds[1] = tuple(bounds[1]) return bounds def _line_dragged(self, line: Optional[_InfiniteLine] = None) -> None: if self._is_dragged: is_start = False else: self._is_dragged = True is_start = True current_pos = self.position if line is self.vline: start_pos = (self.vline.startPosition[0], current_pos[1]) else: start_pos = (current_pos[0], self.hline.startPosition[1]) if line.moving: is_finished = False else: self._is_dragged = False is_finished = True self.sigPositionDragged.emit(start_pos, current_pos, is_start, is_finished) def _line_drag_finished(self, line: Optional[_InfiniteLine] = None) -> None: self._is_dragged = False current_pos = self.position if line is self.vline: start_pos = (self.vline.startPosition[0], current_pos[1]) else: start_pos = (current_pos[0], self.hline.startPosition[1]) self.sigPositionDragged.emit(start_pos, current_pos, False, True) def _line_changed(self, line: Optional[_InfiniteLine] = None) -> None: self.sigPositionChanged.emit(self.position)
[docs] class InfiniteLine(QtCore.QObject): """ Represents a horizontal or vertical infinite line data marker """ _default_pen = {'color': '#00ff00', 'width': 1} _default_hover_pen = {'color': '#ffff00', 'width': 1} sigPositionChanged = QtCore.Signal(object) # current_pos # start_pos, current_pos, is_start, is_finished sigPositionDragged = QtCore.Signal(object, object, bool, bool)
[docs] def __init__(self, viewbox: ViewBox, orientation: QtCore.Qt.Orientation, position: Optional[float] = 0, bounds: Optional[Tuple[Union[None, float]]] = None, movable: Optional[bool] = True, pen: Optional[Any] = None, hover_pen: Optional[Any] = None, ) -> None: super().__init__(parent=viewbox) if position is None: position = 0 if movable is None: movable = True if pen is None: pen = self._default_pen if hover_pen is None: hover_pen = self._default_hover_pen self._bounds = self._normalize_bounds(bounds) self._z_value = None self._is_dragged = False self.line = _InfiniteLine(pos=position, angle=0 if orientation is QtCore.Qt.Horizontal else 90, movable=movable, pen=pen, hoverPen=hover_pen) self.line.sigDragged.connect(self._line_dragged) self.line.sigPositionChanged.connect(self._line_changed) self.line.sigPositionChangeFinished.connect(self._line_drag_finished) self.show()
@property def orientation(self) -> QtCore.Qt.Orientation: return QtCore.Qt.Vertical if self.line.angle == 90 else QtCore.Qt.Horizontal @property def movable(self) -> bool: return bool(self.line.movable) def set_movable(self, movable: bool) -> None: self.line.setMovable(movable) @property def z_value(self) -> Union[None, int]: return self._z_value def set_z_value(self, value: int) -> None: self.line.setZValue(value) self._z_value = value @property def position(self) -> float: return self.line.value() def set_position(self, position: float) -> None: self.line.blockSignals(True) try: self.line.setPos(position) finally: self.line.blockSignals(False) self.sigPositionChanged.emit(self.position) @property def bounds(self) -> Tuple[Union[None, float], Union[None, float]]: return self._bounds def set_bounds(self, bounds: Union[None, Tuple[Union[None, float], Union[None, float]]] ) -> None: """ Sets a range boundary for the line position """ self._bounds = self._normalize_bounds(bounds) self.line.setBounds(self._bounds) def show(self): view = self.parent() if self.line not in view.addedItems: view.addItem(self.line) if self._z_value is not None: self.line.setZValue(self._z_value) def hide(self): view = self.parent() if self.line in view.addedItems: view.removeItem(self.line) def set_pen(self, pen: Any) -> None: """ Sets the pen to be used for drawing the line. Given parameter must be compatible with pyqtgraph.mkPen() """ self.line.setPen(pen) def set_hover_pen(self, pen: Any) -> None: """ Sets the pen to be used for drawing the line when the mouse cursor is hovering over it. Given parameter must be compatible with pyqtgraph.mkPen() """ self.line.setHoverPen(pen) @staticmethod def _normalize_bounds(bounds: Union[None, Tuple[Union[None, float], Union[None, float]]] ) -> Tuple[Union[None, float], Union[None, float]]: if bounds is None: bounds = (None, None) else: # Replace inf values by None try: if isinf(bounds[0]): bounds = (None, bounds[1]) except TypeError: pass try: if isinf(bounds[1]): bounds = (bounds[0], None) except TypeError: pass # Sort spans in ascending order try: bounds = tuple(sorted(bounds)) except TypeError: bounds = tuple(bounds) return bounds def _line_dragged(self, line: Optional[_InfiniteLine] = None) -> None: if self._is_dragged: is_start = False else: self._is_dragged = True is_start = True start_pos = self.line.startPosition[0 if self.line.angle == 90 else 1] self.sigPositionDragged.emit(start_pos, self.position, is_start, False) def _line_drag_finished(self, line: Optional[_InfiniteLine] = None) -> None: self._is_dragged = False start_pos = self.line.startPosition[0 if self.line.angle == 90 else 1] self.sigPositionDragged.emit(start_pos, self.position, False, True) def _line_changed(self, line: Optional[_InfiniteLine] = None) -> None: self.sigPositionChanged.emit(self.position)
[docs] class LinearRegion(QtCore.QObject): """ """ _default_pen = {'color': '#00ff00', 'width': 1} _default_hover_pen = {'color': '#ffff00', 'width': 1} _default_brush = None _default_hover_brush = None sigAreaChanged = QtCore.Signal(tuple) # current_area # start_area, current_area, is_start, is_finished sigAreaDragged = QtCore.Signal(tuple, tuple, bool, bool)
[docs] def __init__(self, viewbox: ViewBox, orientation: QtCore.Qt.Orientation, span: Optional[Tuple[float, float]] = (0, 1), bounds: Optional[Tuple[Union[None, float], Union[None, float]]] = None, movable: Optional[bool] = True, pen: Optional[Any] = None, hover_pen: Optional[Any] = None, brush: Optional[Any] = None, hover_brush: Optional[Any] = None ) -> None: super().__init__(parent=viewbox) if span is None: span = (0, 1) if movable is None: movable = True if pen is None: pen = self._default_pen if hover_pen is None: hover_pen = self._default_hover_pen if brush is None: brush = self._default_brush if hover_brush is None: hover_brush = self._default_hover_brush orientation = 'vertical' if orientation == QtCore.Qt.Vertical else 'horizontal' self._bounds = self._normalize_bounds(bounds) self.region = _LinearRegionItem(values=span, orientation=orientation, brush=brush, pen=pen, hoverBrush=hover_brush, hoverPen=hover_pen, movable=movable, bounds=self._bounds, swapMode='sort') self._z_value = None self._is_dragged = False self._start_area = self.area self.region.sigRegionChangeFinished.connect(self._region_change_finished) self.region.sigRegionChanged.connect(self._region_changed) self.show()
@property def orientation(self) -> QtCore.Qt.Orientation: return QtCore.Qt.Vertical if self.region.orientation == 'vertical' else QtCore.Qt.Horizontal @property def movable(self) -> bool: return self.region.movable def set_movable(self, movable: bool) -> None: self.region.setMovable(movable) @property def z_value(self) -> Union[None, int]: return self._z_value def set_z_value(self, value: int) -> None: self.region.setZValue(value) self._z_value = value @property def area(self) -> Tuple[float, float]: return self.region.getRegion() def set_area(self, area: Tuple[float, float]) -> None: self.region.blockSignals(True) try: self.region.setRegion(area) finally: self.region.blockSignals(False) self.sigAreaChanged.emit(self.area) @property def bounds(self) -> Tuple[Union[None, float], Union[None, float]]: return self._bounds def set_bounds(self, bounds: Union[None, Tuple[Union[None, float], Union[None, float]]] ) -> None: self._bounds = self._normalize_bounds(bounds) self.region.setBounds(self._bounds) def show(self): view = self.parent() if self.region not in view.addedItems: view.addItem(self.region) if self._z_value is not None: self.region.setZValue(self._z_value) def hide(self): view = self.parent() if self.region in view.addedItems: view.removeItem(self.region) def set_pen(self, pen: Any) -> None: """ Sets the pen to be used for drawing the lines. Given parameter must be compatible with pyqtgraph.mkPen() """ for line in self.region.lines: line.setPen(pen) def set_hover_pen(self, pen: Any) -> None: """ Sets the pen to be used for drawing the lines when the mouse cursor is hovering over it. Given parameter must be compatible with pyqtgraph.mkPen() """ for line in self.region.lines: line.setHoverPen(pen) def set_brush(self, brush: Any) -> None: """ Sets the brush to be used for filling the area between the lines. Given parameter must be compatible with pyqtgraph.mkBrush() """ self.region.setBrush(brush) def set_hover_brush(self, brush: Any) -> None: """ Sets the brush to be used for filling the area between the lines when the mouse cursor is hovering over it. Given parameter must be compatible with pyqtgraph.mkBrush() """ self.region.setHoverBrush(brush) @staticmethod def _normalize_bounds(bounds: Union[None, Tuple[Union[None, float], Union[None, float]]] ) -> Tuple[Union[None, float], Union[None, float]]: if bounds is None: bounds = (None, None) else: # Replace inf values by None try: if isinf(bounds[0]): bounds = (None, bounds[1]) except TypeError: pass try: if isinf(bounds[1]): bounds = (bounds[0], None) except TypeError: pass # Sort spans in ascending order try: bounds = tuple(sorted(bounds)) except TypeError: bounds = tuple(bounds) return bounds def _region_changed(self, obj: Optional[_LinearRegionItem] = None) -> None: if self._is_dragged: is_start = False else: self._is_dragged = True is_start = True if self.region.orientation == 'vertical': self._start_area = tuple(pos[0] for pos in self.region.startPositions) else: self._start_area = tuple(pos[1] for pos in self.region.startPositions) if self.region.moving: is_finished = False else: self._is_dragged = False is_finished = True self.sigAreaDragged.emit(self._start_area, self.area, is_start, is_finished) def _region_change_finished(self, obj: Optional[_LinearRegionItem] = None) -> None: current_area = self.area if self._is_dragged: self._is_dragged = False self.sigAreaDragged.emit(self._start_area, current_area, False, True) self.sigAreaChanged.emit(current_area)
[docs] class Rectangle(QtCore.QObject): """ """ _default_pen = {'color': '#00ff00', 'width': 1} _default_hover_pen = {'color': '#ffff00', 'width': 1} sigAreaChanged = QtCore.Signal(tuple) # current_area # start_area, current_area, is_start, is_finished sigAreaDragged = QtCore.Signal(tuple, tuple, bool, bool)
[docs] def __init__(self, viewbox: ViewBox, position: Optional[Tuple[float, float]] = (0, 0), size: Optional[Tuple[float, float]] = (1, 1), edge_handles: Optional[bool] = False, corner_handles: Optional[bool] = False, apply_bounds_to_center: Optional[bool] = False, bounds: Optional[Sequence[Tuple[Union[None, float], Union[None, float]]]] = None, movable: Optional[bool] = True, resizable: Optional[bool] = True, pen: Optional[Any] = None, hover_pen: Optional[Any] = None ) -> None: super().__init__(parent=viewbox) if position is None: position = (0, 0) if size is None: size = (1, 1) if movable is None: movable = True if resizable is None: resizable = True if pen is None: pen = self._default_pen if hover_pen is None: hover_pen = self._default_hover_pen self.roi = _RectangleROI(pos=position, size=size, apply_bounds_to_center=apply_bounds_to_center, bounds=bounds, movable=movable, resizable=resizable, pen=pen, hoverPen=hover_pen, handlePen=pen, handleHoverPen=hover_pen) self.roi.sigRegionChanged.connect(self._roi_changed) self.roi.sigRegionChangeFinished.connect(self._roi_change_finished) self.roi.sigRegionChangeStarted.connect(self._roi_change_started) self._z_value = None self._corner_handles = bool(corner_handles) self._edge_handles = bool(edge_handles) self._is_dragged = False self._start_area = self.area if resizable: self._add_handles() self.show()
@property def movable(self) -> bool: return self.roi.translatable def set_movable(self, movable: bool) -> None: self.roi.translatable = bool(movable) @property def resizable(self) -> bool: return self.roi.resizable def set_resizable(self, resizable: bool) -> None: resizable = bool(resizable) changed = resizable != self.roi.resizable self.roi.resizable = resizable if changed: if resizable: self._add_handles() else: self._remove_handles() @property def z_value(self) -> Union[None, int]: return self._z_value def set_z_value(self, value: int) -> None: """ (Un-)Set the crosshair movable (draggable by mouse cursor) """ self.roi.setZValue(value) self._z_value = value @property def position(self) -> Tuple[float, float]: return self.roi.area[0] def set_position(self, position: Tuple[float, float]) -> None: if not self._is_dragged: self.roi.set_area(position=position) @property def size(self) -> Tuple[float, float]: return self.roi.area[1] def set_size(self, size: Tuple[float, float]) -> None: if not self._is_dragged: self.roi.set_area(size=(abs(size[0]), abs(size[1]))) @property def min_size(self) -> Tuple[float, float]: return self.roi.min_size def set_min_size(self, size: Tuple[float, float]) -> None: if not self._is_dragged: self.roi.set_min_size(size) @property def area(self) -> Tuple[Tuple[float, float], Tuple[float, float]]: return self.roi.area def set_area(self, position: Optional[Tuple[float, float]] = None, size: Optional[Tuple[float, float]] = None ) -> None: if not self._is_dragged: self.roi.set_area(position=position, size=size) @property def bounds(self) -> List[Tuple[Union[None, float], Union[None, float]]]: return self.roi.bounds def set_bounds(self, bounds: Union[None, Sequence[Tuple[Union[None, float], Union[None, float]]]] ) -> None: return self.roi.set_bounds(bounds) def show(self): view = self.parent() if self.roi not in view.addedItems: view.addItem(self.roi) if self._z_value is not None: self.roi.setZValue(self._z_value) def hide(self): view = self.parent() if self.roi in view.addedItems: view.removeItem(self.roi) def set_pen(self, pen: Any) -> None: """ Given parameter must be compatible with pyqtgraph.mkPen() """ self.roi.setPen(pen) def set_hover_pen(self, pen: Any) -> None: """ Given parameter must be compatible with pyqtgraph.mkPen() """ self.roi.setHoverPen(pen) def _add_handles(self) -> None: if self._corner_handles: self.roi.addScaleHandle([1, 1], [0, 0]) self.roi.addScaleHandle([0, 0], [1, 1]) self.roi.addScaleHandle([1, 0], [0, 1]) self.roi.addScaleHandle([0, 1], [1, 0]) if self._edge_handles: self.roi.addScaleHandle([1, 0.5], [0, 0.5]) self.roi.addScaleHandle([0.5, 1], [0.5, 0]) self.roi.addScaleHandle([0, 0.5], [1, 0.5]) self.roi.addScaleHandle([0.5, 0], [0.5, 1]) def _remove_handles(self) -> None: while len(self.roi.handles) > 0: self.roi.removeHandle(-1) def _roi_change_started(self, roi: Optional[_ROI] = None) -> None: self._is_dragged = True self._start_area = self.area self.sigAreaDragged.emit(self._start_area, self._start_area, True, False) def _roi_change_finished(self, roi: Optional[_ROI] = None) -> None: if self._is_dragged: self._is_dragged = False self.sigAreaDragged.emit(self._start_area, self.area, False, True) def _roi_changed(self, roi: Optional[_ROI] = None) -> None: current_area = self.area if self._is_dragged: self.sigAreaDragged.emit(self._start_area, current_area, False, False) self.sigAreaChanged.emit(current_area)
[docs] class InfiniteCrosshairRectangle(Rectangle): """ Represents a crosshair (two perpendicular infinite lines) and a finite sized rectangle ROI around the intersection """
[docs] def __init__(self, viewbox: ViewBox, position: Optional[Tuple[float, float]] = (0, 0), size: Optional[Tuple[float, float]] = (1, 1), edge_handles: Optional[bool] = False, corner_handles: Optional[bool] = False, apply_bounds_to_center: Optional[bool] = True, bounds: Optional[Sequence[Tuple[Union[None, float], Union[None, float]]]] = None, movable: Optional[bool] = True, resizable: Optional[bool] = True, pen: Optional[Any] = None, hover_pen: Optional[Any] = None ) -> None: if pen is None: pen = self._default_pen if hover_pen is None: hover_pen = self._default_hover_pen bounds = _RectangleROI.normalize_bounds(bounds) self.vline = _InfiniteLine(pos=position[0], bounds=bounds[0], angle=90, movable=movable, pen=pen, hoverPen=hover_pen) self.hline = _InfiniteLine(pos=position[1], bounds=bounds[1], angle=0, movable=movable, pen=pen, hoverPen=hover_pen) super().__init__(viewbox=viewbox, position=position, size=size, edge_handles=edge_handles, corner_handles=corner_handles, apply_bounds_to_center=apply_bounds_to_center, bounds=bounds, movable=movable, resizable=resizable, pen=pen, hover_pen=hover_pen) self._line_is_dragged = False self.vline.sigDragged.connect(self._line_dragged) self.vline.sigPositionChanged.connect(self._line_changed) self.vline.sigPositionChangeFinished.connect(self._line_drag_finished) self.hline.sigDragged.connect(self._line_dragged) self.hline.sigPositionChanged.connect(self._line_changed) self.hline.sigPositionChangeFinished.connect(self._line_drag_finished)
def set_movable(self, movable: bool) -> None: movable = bool(movable) self.roi.translatable = movable self.vline.setMovable(movable) self.hline.setMovable(movable) def set_z_value(self, value: int) -> None: """ (Un-)Set the crosshair movable (draggable by mouse cursor) """ if self._z_value is not None: self.roi.setZValue(value) self.vline.setZValue(value) self.hline.setZValue(value) self._z_value = value def set_position(self, position: Tuple[float, float]) -> None: super().set_position(position) if not self._line_is_dragged: self._move_lines_to_roi() def set_size(self, size: Tuple[float, float]) -> None: super().set_size(size) self._move_lines_to_roi() def set_area(self, position: Optional[Tuple[float, float]] = None, size: Optional[Tuple[float, float]] = None ) -> None: super().set_area(position=position, size=size) if not self._line_is_dragged: self._move_lines_to_roi() def set_bounds(self, bounds: Union[None, Sequence[Tuple[Union[None, float], Union[None, float]]]] ) -> None: super().set_bounds(bounds) x_bounds, y_bounds = self.bounds self.vline.setBounds(x_bounds) self.hline.setBounds(y_bounds) def show(self): view = self.parent() if self.hline not in view.addedItems: view.addItem(self.hline) if self.vline not in view.addedItems: view.addItem(self.vline) if self.roi not in view.addedItems: view.addItem(self.roi) self.set_z_value(self._z_value) def hide(self): view = self.parent() if self.roi in view.addedItems: view.removeItem(self.roi) if self.hline in view.addedItems: view.removeItem(self.hline) if self.vline in view.addedItems: view.removeItem(self.vline) def set_pen(self, pen: Any) -> None: """ Given parameter must be compatible with pyqtgraph.mkPen() """ self.roi.setPen(pen) self.vline.setPen(pen) self.hline.setPen(pen) def set_hover_pen(self, pen: Any) -> None: """ Given parameter must be compatible with pyqtgraph.mkPen() """ self.roi.setHoverPen(pen) self.vline.setHoverPen(pen) self.hline.setHoverPen(pen) def _add_handles(self) -> None: if self._corner_handles: self.roi.addScaleHandle([1, 1], [0.5, 0.5]) if self._edge_handles: self.roi.addScaleHandle([1, 0.5], [0.5, 0.5]) self.roi.addScaleHandle([0.5, 1], [0.5, 0.5]) def _move_lines_to_roi(self) -> None: x_pos, y_pos = self.position self.vline.blockSignals(True) self.hline.blockSignals(True) try: self.vline.setValue(x_pos) self.hline.setValue(y_pos) finally: self.vline.blockSignals(False) self.hline.blockSignals(False) def _line_dragged(self, line: Optional[_InfiniteLine] = None) -> None: if self._line_is_dragged: is_start = False else: self._line_is_dragged = True self._start_area = self.area is_start = True new_pos = (self.vline.value(), self.hline.value()) self.roi.blockSignals(True) try: self.roi.set_area(position=new_pos) finally: self.roi.blockSignals(False) current_area = self.roi.area self.sigAreaDragged.emit(self._start_area, current_area, is_start, False) def _line_drag_finished(self, line: Optional[_InfiniteLine] = None) -> None: if self._line_is_dragged: self._line_is_dragged = False self.sigAreaDragged.emit(self._start_area, self.area, False, True) def _line_changed(self, line: Optional[_InfiniteLine] = None) -> None: self.sigAreaChanged.emit(self.area) def _roi_changed(self, roi: Optional[_ROI] = None) -> None: self._move_lines_to_roi() super()._roi_changed(roi)