Skip to content

Commit

Permalink
Merge pull request #3959 from silx-kit/fix_3538
Browse files Browse the repository at this point in the history
PlotWindow: add an option to get tape measure
  • Loading branch information
payno authored Dec 11, 2023
2 parents fcb2711 + 917ff73 commit 71acbac
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 1 deletion.
114 changes: 114 additions & 0 deletions src/silx/gui/plot/PlotToolButtons.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- :class:`.AspectToolButton`
- :class:`.YAxisOriginToolButton`
- :class:`.ProfileToolButton`
- :class:`.RulerToolButton`
- :class:`.SymbolToolButton`
"""
Expand All @@ -41,12 +42,15 @@
import functools
import logging
import weakref
import numpy

from .. import icons
from .. import qt
from ... import config

from .items import SymbolMixIn, Scatter
from silx.gui.plot.tools.roi import RegionOfInterestManager
from silx.gui.plot.items.roi import LineROI


_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -589,3 +593,113 @@ def _binningChanged(self, value):
Scatter.VisualizationParameter.BINNED_STATISTIC_SHAPE,
(value, value))
item.setVisualization(Scatter.Visualization.BINNED_STATISTIC)


class RulerToolButton(PlotToolButton):
"""
Button to active measurement between two point of the plot
An instance of `RulerToolButton` can be added to a plot toolbar like:
.. code-block:: python
plot = Plot2D()
rulerButton = RulerToolButton(parent=plot, plot=plot)
plot.toolBar().addWidget(rulerButton)
"""

class RulerROI(LineROI):
def __init__(self, parent=None):
super().__init__(parent)
self._formatFunction = None

def registerFormatFunction(self, fct):
"""fct is expected to be a function taking (startPoint, endPoint) as parameter"""
self._formatFunction = fct

def setEndPoints(self, startPoint, endPoint):
super().setEndPoints(startPoint=startPoint, endPoint=endPoint)
if self._formatFunction is not None:
ruler_text = self._formatFunction(startPoint=startPoint, endPoint=endPoint)
self._updateText(ruler_text)

def __init__(
self,
parent=None,
plot=None,
color: str="yellow",
):
self.__color = color
super().__init__(parent=parent, plot=plot)
self.setCheckable(True)
self._roiManager = None
self._lastRoiCreated = None
self.setIcon(
icons.getQIcon("ruler")
)
self.toggled.connect(self._callback)
self._connectPlot(plot)

def setPlot(self, plot):
return super().setPlot(plot)

def _callback(self, *args, **kwargs):
if not self._roiManager:
return
if self._lastRoiCreated is not None:
self._lastRoiCreated.setVisible(self.isChecked())
if self.isChecked():
self._roiManager.start(
self.RulerROI,
self,
)
self.__interactiveModeStarted(self._roiManager)
else:
source = self._roiManager.getInteractionSource()
if source is self:
self._roiManager.stop()

def __interactiveModeStarted(self, roiManager):
roiManager.sigInteractiveModeFinished.connect(self.__interactiveModeFinished)

def __interactiveModeFinished(self):
roiManager = self._roiManager
if roiManager is not None:
roiManager.sigInteractiveModeFinished.disconnect(self.__interactiveModeFinished)
self.setChecked(False)

def _connectPlot(self, plot):
"""
Called when the plot is connected to the widget
:param plot: :class:`.PlotWidget` instance
"""
if plot is None:
return
self._roiManager = RegionOfInterestManager(plot)
self._roiManager.setColor(self.__color) # Set the color of ROI
self._roiManager.sigRoiAdded.connect(self._registerCurrentROI)

def _disconnectPlot(self, plot):
if plot and self._lastRoiCreated is not None:
self._roiManager.removeRoi(self._lastRoiCreated)
self._lastRoiCreated = None
return super()._disconnectPlot(plot)

def _registerCurrentROI(self, currentRoi):
if self._lastRoiCreated is None:
self._lastRoiCreated = currentRoi
self._lastRoiCreated.registerFormatFunction(self.buildDistanceText)
elif currentRoi != self._lastRoiCreated and self._roiManager is not None:
self._roiManager.removeRoi(self._lastRoiCreated)
self._lastRoiCreated = currentRoi
self._lastRoiCreated.registerFormatFunction(self.buildDistanceText)

def buildDistanceText(self, startPoint, endPoint):
"""
define the text to be displayed by the ruler.
It can be redefine to modify precision or handle other parameters
(handling pixel size to display metric distance, display distance on each distance - for non-square pixels...)
"""
distance = numpy.linalg.norm(endPoint - startPoint)
return f"{distance: .1f}px"
2 changes: 1 addition & 1 deletion src/silx/gui/plot/PlotWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ def _createToolBar(self, title, parent):
elif obj is self.yAxisInvertedButton:
self.yAxisInvertedAction = toolbar.addWidget(obj)
else:
raise RuntimeError()
raise RuntimeError("unknow action to be defined")
return toolbar

def toolBar(self):
Expand Down
Binary file added src/silx/resources/gui/icons/ruler.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
223 changes: 223 additions & 0 deletions src/silx/resources/gui/icons/ruler.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 71acbac

Please sign in to comment.