Skip to main content
`Ctrl`+`K`

# Source code for silx.gui.plot3d.tools.PositionInfoWidget

    # /*##########################################################################
    #
    # Copyright (c) 2018-2021 European Synchrotron Radiation Facility
    #
    # Permission is hereby granted, free of charge, to any person obtaining a copy
    # of this software and associated documentation files (the "Software"), to deal
    # in the Software without restriction, including without limitation the rights
    # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    # copies of the Software, and to permit persons to whom the Software is
    # furnished to do so, subject to the following conditions:
    #
    # The above copyright notice and this permission notice shall be included in
    # all copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    # THE SOFTWARE.
    #
    # ###########################################################################*/
    """This module provides a widget that displays data values of a SceneWidget.
    """

    __authors__ = ["T. Vincent"]
    __license__ = "MIT"
    __date__ = "01/10/2018"

    import logging
    import weakref

    from ... import qt
    from .. import actions
    from .. import items
    from ..items import volume
    from ..SceneWidget import SceneWidget

    _logger = logging.getLogger(__name__)

    class PositionInfoWidget(qt.QWidget):
        """Widget displaying information about picked position

        :param QWidget parent: See :class:`QWidget`
        """

        def __init__(self, parent=None):
            super(PositionInfoWidget, self).__init__(parent)
            self._sceneWidgetRef = None

            self.setToolTip("Double-click on a data point to show its value")
            layout = qt.QBoxLayout(qt.QBoxLayout.LeftToRight, self)

            self._xLabel = self._addInfoField("X")
            self._yLabel = self._addInfoField("Y")
            self._zLabel = self._addInfoField("Z")
            self._dataLabel = self._addInfoField("Data")
            self._itemLabel = self._addInfoField("Item")

            layout.addStretch(1)

            self._action = actions.mode.PickingModeAction(parent=self)
            self._action.setText("Selection")
            self._action.setToolTip(
                "Toggle selection information update with left button click"
            )
            self._action.sigSceneClicked.connect(self.pick)
            self._action.changed.connect(self.__actionChanged)
            self._action.setChecked(False)  # Disabled by default
            self.__actionChanged()  # Sync action/widget

        def __actionChanged(self):
            """Handle toggle action change signal"""
            if self.toggleAction().isChecked() != self.isEnabled():
                self.setEnabled(self.toggleAction().isChecked())

        def toggleAction(self):
            """The action to toggle the picking mode.

            :rtype: QAction
            """
            return self._action

        def _addInfoField(self, label):
            """Add a description: info widget to this widget

            :param str label: Description label
            :return: The QLabel used to display the info
            :rtype: QLabel
            """
            subLayout = qt.QHBoxLayout()
            subLayout.setContentsMargins(0, 0, 0, 0)

            subLayout.addWidget(qt.QLabel(label + ":"))

            widget = qt.QLabel("-")
            widget.setAlignment(qt.Qt.AlignLeft | qt.Qt.AlignVCenter)
            widget.setTextInteractionFlags(qt.Qt.TextSelectableByMouse)

            metrics = widget.fontMetrics()
            if qt.BINDING == "PyQt5":
                width = metrics.width("#######")
            else:  # Qt6
                width = metrics.horizontalAdvance("#######")
            widget.setMinimumWidth(width)
            subLayout.addWidget(widget)

            subLayout.addStretch(1)

            layout = self.layout()
            layout.addLayout(subLayout)
            return widget

        def getSceneWidget(self):
            """Returns the associated :class:`SceneWidget` or None.

            :rtype: Union[None,~silx.gui.plot3d.SceneWidget.SceneWidget]
            """
            if self._sceneWidgetRef is None:
                return None
            else:
                return self._sceneWidgetRef()

        def setSceneWidget(self, widget):
            """Set the associated :class:`SceneWidget`

            :param ~silx.gui.plot3d.SceneWidget.SceneWidget widget:
                3D scene for which to display information
            """
            if widget is not None and not isinstance(widget, SceneWidget):
                raise ValueError("widget must be a SceneWidget or None")

            self._sceneWidgetRef = None if widget is None else weakref.ref(widget)

            self.toggleAction().setPlot3DWidget(widget)

        def clear(self):
            """Clean-up displayed values"""
            for widget in (
                self._xLabel,
                self._yLabel,
                self._zLabel,
                self._dataLabel,
                self._itemLabel,
            ):
                widget.setText("-")

        _SUPPORTED_ITEMS = (
            items.Scatter3D,
            items.Scatter2D,
            items.ImageData,
            items.ImageRgba,
            items.HeightMapData,
            items.HeightMapRGBA,
            items.Mesh,
            items.Box,
            items.Cylinder,
            items.Hexagon,
            volume.CutPlane,
            volume.Isosurface,
        )
        """Type of items that are picked"""

        def _isSupportedItem(self, item):
            """Returns True if item is of supported type

            :param Item3D item: The Item3D to check
            :rtype: bool
            """
            return isinstance(item, self._SUPPORTED_ITEMS)

        def pick(self, x, y):
            """Pick items in the associated SceneWidget and display result

            Only the closest point is displayed.

            :param int x: X coordinate in pixel in the SceneWidget
            :param int y: Y coordinate in pixel in the SceneWidget
            """
            self.clear()

            sceneWidget = self.getSceneWidget()
            if sceneWidget is None:  # No associated widget
                _logger.info("Picking without associated SceneWidget")
                return

            # Find closest (and latest in the tree) supported item
            closestNdcZ = float("inf")
            picking = None
            for result in sceneWidget.pickItems(x, y, condition=self._isSupportedItem):
                ndcZ = result.getPositions("ndc", copy=False)[0, 2]
                if ndcZ <= closestNdcZ:
                    closestNdcZ = ndcZ
                    picking = result

            if picking is None:
                return  # No picked item

            item = picking.getItem()
            self._itemLabel.setText(item.getLabel())
            positions = picking.getPositions("scene", copy=False)
            x, y, z = positions[0]
            self._xLabel.setText("%g" % x)
            self._yLabel.setText("%g" % y)
            self._zLabel.setText("%g" % z)

            data = picking.getData(copy=False)
            if data is not None:
                data = data[0]
                if hasattr(data, "__len__"):
                    text = " ".join(["%.3g"] * len(data)) % tuple(data)
                else:
                    text = "%g" % data
                self._dataLabel.setText(text)

        def updateInfo(self):
            """Update information according to cursor position"""
            widget = self.getSceneWidget()
            if widget is None:
                _logger.info("Update without associated SceneWidget")
                self.clear()
                return

            position = widget.mapFromGlobal(qt.QCursor.pos())
            self.pick(position.x(), position.y())