summaryrefslogtreecommitdiff
path: root/silx/gui/plot/actions
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/actions')
-rw-r--r--silx/gui/plot/actions/PlotAction.py78
-rw-r--r--silx/gui/plot/actions/PlotToolAction.py150
-rw-r--r--silx/gui/plot/actions/__init__.py42
-rwxr-xr-xsilx/gui/plot/actions/control.py694
-rw-r--r--silx/gui/plot/actions/fit.py403
-rw-r--r--silx/gui/plot/actions/histogram.py392
-rw-r--r--silx/gui/plot/actions/io.py818
-rw-r--r--silx/gui/plot/actions/medfilt.py147
-rw-r--r--silx/gui/plot/actions/mode.py104
9 files changed, 0 insertions, 2828 deletions
diff --git a/silx/gui/plot/actions/PlotAction.py b/silx/gui/plot/actions/PlotAction.py
deleted file mode 100644
index 2983775..0000000
--- a/silx/gui/plot/actions/PlotAction.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2017 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.
-#
-# ###########################################################################*/
-"""
-The class :class:`.PlotAction` help the creation of a qt.QAction associated
-with a :class:`.PlotWidget`.
-"""
-
-from __future__ import division
-
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "03/01/2018"
-
-
-import weakref
-from silx.gui import icons
-from silx.gui import qt
-
-
-class PlotAction(qt.QAction):
- """Base class for QAction that operates on a PlotWidget.
-
- :param plot: :class:`.PlotWidget` instance on which to operate.
- :param icon: QIcon or str name of icon to use
- :param str text: The name of this action to be used for menu label
- :param str tooltip: The text of the tooltip
- :param triggered: The callback to connect to the action's triggered
- signal or None for no callback.
- :param bool checkable: True for checkable action, False otherwise (default)
- :param parent: See :class:`QAction`.
- """
-
- def __init__(self, plot, icon, text, tooltip=None,
- triggered=None, checkable=False, parent=None):
- assert plot is not None
- self._plotRef = weakref.ref(plot)
-
- if not isinstance(icon, qt.QIcon):
- # Try with icon as a string and load corresponding icon
- icon = icons.getQIcon(icon)
-
- super(PlotAction, self).__init__(icon, text, parent)
-
- if tooltip is not None:
- self.setToolTip(tooltip)
-
- self.setCheckable(checkable)
-
- if triggered is not None:
- self.triggered[bool].connect(triggered)
-
- @property
- def plot(self):
- """The :class:`.PlotWidget` this action group is controlling."""
- return self._plotRef()
diff --git a/silx/gui/plot/actions/PlotToolAction.py b/silx/gui/plot/actions/PlotToolAction.py
deleted file mode 100644
index fbb0b0f..0000000
--- a/silx/gui/plot/actions/PlotToolAction.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2020 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.
-#
-# ###########################################################################*/
-"""
-The class :class:`.PlotToolAction` help the creation of a qt.QAction associating
-a tool window with a :class:`.PlotWidget`.
-"""
-
-from __future__ import division
-
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "10/10/2018"
-
-
-import weakref
-
-from .PlotAction import PlotAction
-from silx.gui import qt
-
-
-class PlotToolAction(PlotAction):
- """Base class for QAction that maintain a tool window operating on a
- PlotWidget."""
-
- def __init__(self, plot, icon, text, tooltip=None,
- triggered=None, checkable=False, parent=None):
- PlotAction.__init__(self,
- plot=plot,
- icon=icon,
- text=text,
- tooltip=tooltip,
- triggered=self._triggered,
- parent=parent,
- checkable=True)
- self._previousGeometry = None
- self._toolWindow = None
-
- def _triggered(self, checked):
- """Update the plot of the histogram visibility status
-
- :param bool checked: status of the action button
- """
- self._setToolWindowVisible(checked)
-
- def _setToolWindowVisible(self, visible):
- """Set the tool window visible or hidden."""
- tool = self._getToolWindow()
- if tool.isVisible() == visible:
- # Nothing to do
- return
-
- if visible:
- self._connectPlot(tool)
- tool.show()
- if self._previousGeometry is not None:
- # Restore the geometry
- tool.setGeometry(self._previousGeometry)
- else:
- self._disconnectPlot(tool)
- # Save the geometry
- self._previousGeometry = tool.geometry()
- tool.hide()
-
- def _connectPlot(self, window):
- """Called if the tool is visible and have to be updated according to
- event of the plot.
-
- :param qt.QWidget window: The tool window
- """
- pass
-
- def _disconnectPlot(self, window):
- """Called if the tool is not visible and dont have anymore to be updated
- according to event of the plot.
-
- :param qt.QWidget window: The tool window
- """
- pass
-
- def _isWindowInUse(self):
- """Returns true if the tool window is currently in use."""
- if not self.isChecked():
- return False
- return self._toolWindow is not None
-
- def _ownerVisibilityChanged(self, isVisible):
- """Called when the visibility of the parent of the tool window changes
-
- :param bool isVisible: True if the parent became visible
- """
- if self._isWindowInUse():
- self._setToolWindowVisible(isVisible)
-
- def eventFilter(self, qobject, event):
- """Observe when the close event is emitted then
- simply uncheck the action button
-
- :param qobject: the object observe
- :param event: the event received by qobject
- """
- if event.type() == qt.QEvent.Close:
- if self._toolWindow is not None:
- window = self._toolWindow()
- self._previousGeometry = window.geometry()
- window.hide()
- self.setChecked(False)
-
- return PlotAction.eventFilter(self, qobject, event)
-
- def _getToolWindow(self):
- """Returns the window containing the tool.
-
- It uses lazy loading to create this tool..
- """
- if self._toolWindow is None:
- window = self._createToolWindow()
- if self._previousGeometry is not None:
- window.setGeometry(self._previousGeometry)
- window.installEventFilter(self)
- plot = self.plot
- plot.sigVisibilityChanged.connect(self._ownerVisibilityChanged)
- self._toolWindow = weakref.ref(window)
- return self._toolWindow()
-
- def _createToolWindow(self):
- """Create the tool window managing the plot."""
- raise NotImplementedError()
diff --git a/silx/gui/plot/actions/__init__.py b/silx/gui/plot/actions/__init__.py
deleted file mode 100644
index 930c728..0000000
--- a/silx/gui/plot/actions/__init__.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2017-2018 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 package provides a set of QAction to use with
-:class:`~silx.gui.plot.PlotWidget`
-
-Those actions are useful to add menu items or toolbar items
-that interact with a :class:`~silx.gui.plot.PlotWidget`.
-
-It provides a base class used to define new plot actions:
-:class:`~silx.gui.plot.actions.PlotAction`.
-"""
-
-__authors__ = ["H. Payno"]
-__license__ = "MIT"
-__date__ = "16/08/2017"
-
-from .PlotAction import PlotAction
-from . import control
-from . import mode
-from . import io
diff --git a/silx/gui/plot/actions/control.py b/silx/gui/plot/actions/control.py
deleted file mode 100755
index 439985e..0000000
--- a/silx/gui/plot/actions/control.py
+++ /dev/null
@@ -1,694 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2019 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.
-#
-# ###########################################################################*/
-"""
-:mod:`silx.gui.plot.actions.control` provides a set of QAction relative to control
-of a :class:`.PlotWidget`.
-
-The following QAction are available:
-
-- :class:`ColormapAction`
-- :class:`CrosshairAction`
-- :class:`CurveStyleAction`
-- :class:`GridAction`
-- :class:`KeepAspectRatioAction`
-- :class:`PanWithArrowKeysAction`
-- :class:`ResetZoomAction`
-- :class:`ShowAxisAction`
-- :class:`XAxisLogarithmicAction`
-- :class:`XAxisAutoScaleAction`
-- :class:`YAxisInvertedAction`
-- :class:`YAxisLogarithmicAction`
-- :class:`YAxisAutoScaleAction`
-- :class:`ZoomBackAction`
-- :class:`ZoomInAction`
-- :class:`ZoomOutAction`
-"""
-
-from __future__ import division
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "27/11/2020"
-
-from . import PlotAction
-import logging
-from silx.gui.plot import items
-from silx.gui.plot._utils import applyZoomToPlot as _applyZoomToPlot
-from silx.gui import qt
-from silx.gui import icons
-
-_logger = logging.getLogger(__name__)
-
-
-class ResetZoomAction(PlotAction):
- """QAction controlling reset zoom on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(ResetZoomAction, self).__init__(
- plot, icon='zoom-original', text='Reset Zoom',
- tooltip='Auto-scale the graph',
- triggered=self._actionTriggered,
- checkable=False, parent=parent)
- self._autoscaleChanged(True)
- plot.getXAxis().sigAutoScaleChanged.connect(self._autoscaleChanged)
- plot.getYAxis().sigAutoScaleChanged.connect(self._autoscaleChanged)
-
- def _autoscaleChanged(self, enabled):
- xAxis = self.plot.getXAxis()
- yAxis = self.plot.getYAxis()
- self.setEnabled(xAxis.isAutoScale() or yAxis.isAutoScale())
-
- if xAxis.isAutoScale() and yAxis.isAutoScale():
- tooltip = 'Auto-scale the graph'
- elif xAxis.isAutoScale(): # And not Y axis
- tooltip = 'Auto-scale the x-axis of the graph only'
- elif yAxis.isAutoScale(): # And not X axis
- tooltip = 'Auto-scale the y-axis of the graph only'
- else: # no axis in autoscale
- tooltip = 'Auto-scale the graph'
- self.setToolTip(tooltip)
-
- def _actionTriggered(self, checked=False):
- self.plot.resetZoom()
-
-
-class ZoomBackAction(PlotAction):
- """QAction performing a zoom-back in :class:`.PlotWidget` limits history.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(ZoomBackAction, self).__init__(
- plot, icon='zoom-back', text='Zoom Back',
- tooltip='Zoom back the plot',
- triggered=self._actionTriggered,
- checkable=False, parent=parent)
- self.setShortcutContext(qt.Qt.WidgetShortcut)
-
- def _actionTriggered(self, checked=False):
- self.plot.getLimitsHistory().pop()
-
-
-class ZoomInAction(PlotAction):
- """QAction performing a zoom-in on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(ZoomInAction, self).__init__(
- plot, icon='zoom-in', text='Zoom In',
- tooltip='Zoom in the plot',
- triggered=self._actionTriggered,
- checkable=False, parent=parent)
- self.setShortcut(qt.QKeySequence.ZoomIn)
- self.setShortcutContext(qt.Qt.WidgetShortcut)
-
- def _actionTriggered(self, checked=False):
- _applyZoomToPlot(self.plot, 1.1)
-
-
-class ZoomOutAction(PlotAction):
- """QAction performing a zoom-out on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(ZoomOutAction, self).__init__(
- plot, icon='zoom-out', text='Zoom Out',
- tooltip='Zoom out the plot',
- triggered=self._actionTriggered,
- checkable=False, parent=parent)
- self.setShortcut(qt.QKeySequence.ZoomOut)
- self.setShortcutContext(qt.Qt.WidgetShortcut)
-
- def _actionTriggered(self, checked=False):
- _applyZoomToPlot(self.plot, 1. / 1.1)
-
-
-class XAxisAutoScaleAction(PlotAction):
- """QAction controlling X axis autoscale on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(XAxisAutoScaleAction, self).__init__(
- plot, icon='plot-xauto', text='X Autoscale',
- tooltip='Enable x-axis auto-scale when checked.\n'
- 'If unchecked, x-axis does not change when reseting zoom.',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.setChecked(plot.getXAxis().isAutoScale())
- plot.getXAxis().sigAutoScaleChanged.connect(self.setChecked)
-
- def _actionTriggered(self, checked=False):
- self.plot.getXAxis().setAutoScale(checked)
- if checked:
- self.plot.resetZoom()
-
-
-class YAxisAutoScaleAction(PlotAction):
- """QAction controlling Y axis autoscale on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(YAxisAutoScaleAction, self).__init__(
- plot, icon='plot-yauto', text='Y Autoscale',
- tooltip='Enable y-axis auto-scale when checked.\n'
- 'If unchecked, y-axis does not change when reseting zoom.',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.setChecked(plot.getYAxis().isAutoScale())
- plot.getYAxis().sigAutoScaleChanged.connect(self.setChecked)
-
- def _actionTriggered(self, checked=False):
- self.plot.getYAxis().setAutoScale(checked)
- if checked:
- self.plot.resetZoom()
-
-
-class XAxisLogarithmicAction(PlotAction):
- """QAction controlling X axis log scale on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(XAxisLogarithmicAction, self).__init__(
- plot, icon='plot-xlog', text='X Log. scale',
- tooltip='Logarithmic x-axis when checked',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.axis = plot.getXAxis()
- self.setChecked(self.axis.getScale() == self.axis.LOGARITHMIC)
- self.axis.sigScaleChanged.connect(self._setCheckedIfLogScale)
-
- def _setCheckedIfLogScale(self, scale):
- self.setChecked(scale == self.axis.LOGARITHMIC)
-
- def _actionTriggered(self, checked=False):
- scale = self.axis.LOGARITHMIC if checked else self.axis.LINEAR
- self.axis.setScale(scale)
-
-
-class YAxisLogarithmicAction(PlotAction):
- """QAction controlling Y axis log scale on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(YAxisLogarithmicAction, self).__init__(
- plot, icon='plot-ylog', text='Y Log. scale',
- tooltip='Logarithmic y-axis when checked',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.axis = plot.getYAxis()
- self.setChecked(self.axis.getScale() == self.axis.LOGARITHMIC)
- self.axis.sigScaleChanged.connect(self._setCheckedIfLogScale)
-
- def _setCheckedIfLogScale(self, scale):
- self.setChecked(scale == self.axis.LOGARITHMIC)
-
- def _actionTriggered(self, checked=False):
- scale = self.axis.LOGARITHMIC if checked else self.axis.LINEAR
- self.axis.setScale(scale)
-
-
-class GridAction(PlotAction):
- """QAction controlling grid mode on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param str gridMode: The grid mode to use in 'both', 'major'.
- See :meth:`.PlotWidget.setGraphGrid`
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, gridMode='both', parent=None):
- assert gridMode in ('both', 'major')
- self._gridMode = gridMode
-
- super(GridAction, self).__init__(
- plot, icon='plot-grid', text='Grid',
- tooltip='Toggle grid (on/off)',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.setChecked(plot.getGraphGrid() is not None)
- plot.sigSetGraphGrid.connect(self._gridChanged)
-
- def _gridChanged(self, which):
- """Slot listening for PlotWidget grid mode change."""
- self.setChecked(which != 'None')
-
- def _actionTriggered(self, checked=False):
- self.plot.setGraphGrid(self._gridMode if checked else None)
-
-
-class CurveStyleAction(PlotAction):
- """QAction controlling curve style on a :class:`.PlotWidget`.
-
- It changes the default line and markers style which updates all
- curves on the plot.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(CurveStyleAction, self).__init__(
- plot, icon='plot-toggle-points', text='Curve style',
- tooltip='Change curve line and markers style',
- triggered=self._actionTriggered,
- checkable=False, parent=parent)
-
- def _actionTriggered(self, checked=False):
- currentState = (self.plot.isDefaultPlotLines(),
- self.plot.isDefaultPlotPoints())
-
- if currentState == (False, False):
- newState = True, False
- else:
- # line only, line and symbol, symbol only
- states = (True, False), (True, True), (False, True)
- newState = states[(states.index(currentState) + 1) % 3]
-
- self.plot.setDefaultPlotLines(newState[0])
- self.plot.setDefaultPlotPoints(newState[1])
-
-
-class ColormapAction(PlotAction):
- """QAction opening a ColormapDialog to update the colormap.
-
- Both the active image colormap and the default colormap are updated.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- self._dialog = None # To store an instance of ColormapDialog
- super(ColormapAction, self).__init__(
- plot, icon='colormap', text='Colormap',
- tooltip="Change colormap",
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.plot.sigActiveImageChanged.connect(self._updateColormap)
- self.plot.sigActiveScatterChanged.connect(self._updateColormap)
-
- def setColorDialog(self, colorDialog):
- """Set a specific color dialog instead of using the default dialog."""
- assert(colorDialog is not None)
- assert(self._dialog is None)
- self._dialog = colorDialog
- self._dialog.visibleChanged.connect(self._dialogVisibleChanged)
- self.setChecked(self._dialog.isVisible())
-
- @staticmethod
- def _createDialog(parent):
- """Create the dialog if not already existing
-
- :parent QWidget parent: Parent of the new colormap
- :rtype: ColormapDialog
- """
- from silx.gui.dialog.ColormapDialog import ColormapDialog
- dialog = ColormapDialog(parent=parent)
- dialog.setModal(False)
- return dialog
-
- def _actionTriggered(self, checked=False):
- """Create a cmap dialog and update active image and default cmap."""
- if self._dialog is None:
- self._dialog = self._createDialog(self.plot)
- self._dialog.visibleChanged.connect(self._dialogVisibleChanged)
-
- # Run the dialog listening to colormap change
- if checked is True:
- self._updateColormap()
- self._dialog.show()
- else:
- self._dialog.hide()
-
- def _dialogVisibleChanged(self, isVisible):
- self.setChecked(isVisible)
-
- def _updateColormap(self):
- if self._dialog is None:
- return
- image = self.plot.getActiveImage()
-
- if isinstance(image, items.ColormapMixIn):
- # Set dialog from active image
- colormap = image.getColormap()
- # Set histogram and range if any
- self._dialog.setItem(image)
-
- else:
- # No active image or active image is RGBA,
- # Check for active scatter plot
- scatter = self.plot._getActiveItem(kind='scatter')
- if scatter is not None:
- colormap = scatter.getColormap()
- self._dialog.setItem(scatter)
-
- else:
- # No active data image nor scatter,
- # set dialog from default info
- colormap = self.plot.getDefaultColormap()
- # Reset histogram and range if any
- self._dialog.setData(None)
-
- self._dialog.setColormap(colormap)
-
-
-class ColorBarAction(PlotAction):
- """QAction opening the ColorBarWidget of the specified plot.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- self._dialog = None # To store an instance of ColorBar
- super(ColorBarAction, self).__init__(
- plot, icon='colorbar', text='Colorbar',
- tooltip="Show/Hide the colorbar",
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- colorBarWidget = self.plot.getColorBarWidget()
- old = self.blockSignals(True)
- self.setChecked(colorBarWidget.isVisibleTo(self.plot))
- self.blockSignals(old)
- colorBarWidget.sigVisibleChanged.connect(self._widgetVisibleChanged)
-
- def _widgetVisibleChanged(self, isVisible):
- """Callback when the colorbar `visible` property change."""
- if self.isChecked() == isVisible:
- return
- self.setChecked(isVisible)
-
- def _actionTriggered(self, checked=False):
- """Create a cmap dialog and update active image and default cmap."""
- colorBarWidget = self.plot.getColorBarWidget()
- if not colorBarWidget.isHidden() == checked:
- return
- self.plot.getColorBarWidget().setVisible(checked)
-
-
-class KeepAspectRatioAction(PlotAction):
- """QAction controlling aspect ratio on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- # Uses two images for checked/unchecked states
- self._states = {
- False: (icons.getQIcon('shape-circle-solid'),
- "Keep data aspect ratio"),
- True: (icons.getQIcon('shape-ellipse-solid'),
- "Do no keep data aspect ratio")
- }
-
- icon, tooltip = self._states[plot.isKeepDataAspectRatio()]
- super(KeepAspectRatioAction, self).__init__(
- plot,
- icon=icon,
- text='Toggle keep aspect ratio',
- tooltip=tooltip,
- triggered=self._actionTriggered,
- checkable=False,
- parent=parent)
- plot.sigSetKeepDataAspectRatio.connect(
- self._keepDataAspectRatioChanged)
-
- def _keepDataAspectRatioChanged(self, aspectRatio):
- """Handle Plot set keep aspect ratio signal"""
- icon, tooltip = self._states[aspectRatio]
- self.setIcon(icon)
- self.setToolTip(tooltip)
-
- def _actionTriggered(self, checked=False):
- # This will trigger _keepDataAspectRatioChanged
- self.plot.setKeepDataAspectRatio(not self.plot.isKeepDataAspectRatio())
-
-
-class YAxisInvertedAction(PlotAction):
- """QAction controlling Y orientation on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- # Uses two images for checked/unchecked states
- self._states = {
- False: (icons.getQIcon('plot-ydown'),
- "Orient Y axis downward"),
- True: (icons.getQIcon('plot-yup'),
- "Orient Y axis upward"),
- }
-
- icon, tooltip = self._states[plot.getYAxis().isInverted()]
- super(YAxisInvertedAction, self).__init__(
- plot,
- icon=icon,
- text='Invert Y Axis',
- tooltip=tooltip,
- triggered=self._actionTriggered,
- checkable=False,
- parent=parent)
- plot.getYAxis().sigInvertedChanged.connect(self._yAxisInvertedChanged)
-
- def _yAxisInvertedChanged(self, inverted):
- """Handle Plot set y axis inverted signal"""
- icon, tooltip = self._states[inverted]
- self.setIcon(icon)
- self.setToolTip(tooltip)
-
- def _actionTriggered(self, checked=False):
- # This will trigger _yAxisInvertedChanged
- yAxis = self.plot.getYAxis()
- yAxis.setInverted(not yAxis.isInverted())
-
-
-class CrosshairAction(PlotAction):
- """QAction toggling crosshair cursor on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param str color: Color to use to draw the crosshair
- :param int linewidth: Width of the crosshair cursor
- :param str linestyle: Style of line. See :meth:`.Plot.setGraphCursor`
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, color='black', linewidth=1, linestyle='-',
- parent=None):
- self.color = color
- """Color used to draw the crosshair (str)."""
-
- self.linewidth = linewidth
- """Width of the crosshair cursor (int)."""
-
- self.linestyle = linestyle
- """Style of line of the cursor (str)."""
-
- super(CrosshairAction, self).__init__(
- plot, icon='crosshair', text='Crosshair Cursor',
- tooltip='Enable crosshair cursor when checked',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.setChecked(plot.getGraphCursor() is not None)
- plot.sigSetGraphCursor.connect(self.setChecked)
-
- def _actionTriggered(self, checked=False):
- self.plot.setGraphCursor(checked,
- color=self.color,
- linestyle=self.linestyle,
- linewidth=self.linewidth)
-
-
-class PanWithArrowKeysAction(PlotAction):
- """QAction toggling pan with arrow keys on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
-
- super(PanWithArrowKeysAction, self).__init__(
- plot, icon='arrow-keys', text='Pan with arrow keys',
- tooltip='Enable pan with arrow keys when checked',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- self.setChecked(plot.isPanWithArrowKeys())
- plot.sigSetPanWithArrowKeys.connect(self.setChecked)
-
- def _actionTriggered(self, checked=False):
- self.plot.setPanWithArrowKeys(checked)
-
-
-class ShowAxisAction(PlotAction):
- """QAction controlling axis visibility on a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- tooltip = 'Show plot axis when checked, otherwise hide them'
- PlotAction.__init__(self,
- plot,
- icon='axis',
- text='show axis',
- tooltip=tooltip,
- triggered=self._actionTriggered,
- checkable=True,
- parent=parent)
- self.setChecked(self.plot.isAxesDisplayed())
- plot._sigAxesVisibilityChanged.connect(self.setChecked)
-
- def _actionTriggered(self, checked=False):
- self.plot.setAxesDisplayed(checked)
-
-
-class ClosePolygonInteractionAction(PlotAction):
- """QAction controlling closure of a polygon in draw interaction mode
- if the :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- tooltip = 'Close the current polygon drawn'
- PlotAction.__init__(self,
- plot,
- icon='add-shape-polygon',
- text='Close the polygon',
- tooltip=tooltip,
- triggered=self._actionTriggered,
- checkable=True,
- parent=parent)
- self.plot.sigInteractiveModeChanged.connect(self._modeChanged)
- self._modeChanged(None)
-
- def _modeChanged(self, source):
- mode = self.plot.getInteractiveMode()
- enabled = "shape" in mode and mode["shape"] == "polygon"
- self.setEnabled(enabled)
-
- def _actionTriggered(self, checked=False):
- self.plot._eventHandler.validate()
-
-
-class OpenGLAction(PlotAction):
- """QAction controlling rendering of a :class:`.PlotWidget`.
-
- For now it can enable or not the OpenGL backend.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- # Uses two images for checked/unchecked states
- self._states = {
- "opengl": (icons.getQIcon('backend-opengl'),
- "OpenGL rendering (fast)\nClick to disable OpenGL"),
- "matplotlib": (icons.getQIcon('backend-opengl'),
- "Matplotlib rendering (safe)\nClick to enable OpenGL"),
- "unknown": (icons.getQIcon('backend-opengl'),
- "Custom rendering")
- }
-
- name = self._getBackendName(plot)
- self.__state = name
- icon, tooltip = self._states[name]
- super(OpenGLAction, self).__init__(
- plot,
- icon=icon,
- text='Enable/disable OpenGL rendering',
- tooltip=tooltip,
- triggered=self._actionTriggered,
- checkable=True,
- parent=parent)
-
- def _backendUpdated(self):
- name = self._getBackendName(self.plot)
- self.__state = name
- icon, tooltip = self._states[name]
- self.setIcon(icon)
- self.setToolTip(tooltip)
- self.setChecked(name == "opengl")
-
- def _getBackendName(self, plot):
- backend = plot.getBackend()
- name = type(backend).__name__.lower()
- if "opengl" in name:
- return "opengl"
- elif "matplotlib" in name:
- return "matplotlib"
- else:
- return "unknown"
-
- def _actionTriggered(self, checked=False):
- plot = self.plot
- name = self._getBackendName(self.plot)
- if self.__state != name:
- # THere is no event to know the backend was updated
- # So here we check if there is a mismatch between the displayed state
- # and the real state of the widget
- self._backendUpdated()
- return
- if name != "opengl":
- from silx.gui.utils import glutils
- result = glutils.isOpenGLAvailable()
- if not result:
- qt.QMessageBox.critical(plot, "OpenGL rendering not available", result.error)
- # Uncheck if needed
- self._backendUpdated()
- return
- plot.setBackend("opengl")
- else:
- plot.setBackend("matplotlib")
- self._backendUpdated()
diff --git a/silx/gui/plot/actions/fit.py b/silx/gui/plot/actions/fit.py
deleted file mode 100644
index f3c9e1c..0000000
--- a/silx/gui/plot/actions/fit.py
+++ /dev/null
@@ -1,403 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2020 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.
-#
-# ###########################################################################*/
-"""
-:mod:`silx.gui.plot.actions.fit` module provides actions relative to fit.
-
-The following QAction are available:
-
-- :class:`.FitAction`
-
-.. autoclass:`.FitAction`
-"""
-
-from __future__ import division
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "10/10/2018"
-
-import logging
-
-import numpy
-
-from .PlotToolAction import PlotToolAction
-from .. import items
-from ....utils.deprecation import deprecated
-from silx.gui import qt
-from silx.gui.plot.ItemsSelectionDialog import ItemsSelectionDialog
-
-_logger = logging.getLogger(__name__)
-
-
-def _getUniqueCurveOrHistogram(plot):
- """Returns unique :class:`Curve` or :class:`Histogram` in a `PlotWidget`.
-
- If there is an active curve, returns it, else return curve or histogram
- only if alone in the plot.
-
- :param PlotWidget plot:
- :rtype: Union[None,~silx.gui.plot.items.Curve,~silx.gui.plot.items.Histogram]
- """
- curve = plot.getActiveCurve()
- if curve is not None:
- return curve
-
- histograms = [item for item in plot.getItems()
- if isinstance(item, items.Histogram) and item.isVisible()]
- curves = [item for item in plot.getItems()
- if isinstance(item, items.Curve) and item.isVisible()]
-
- if len(histograms) == 1 and len(curves) == 0:
- return histograms[0]
- elif len(curves) == 1 and len(histograms) == 0:
- return curves[0]
- else:
- return None
-
-
-class FitAction(PlotToolAction):
- """QAction to open a :class:`FitWidget` and set its data to the
- active curve if any, or to the first curve.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- self.__item = None
- self.__activeCurveSynchroEnabled = False
- self.__range = 0, 1
- self.__rangeAutoUpdate = False
- self.__x, self.__y = None, None # Data to fit
- self.__curveParams = {} # Store curve parameters to use for fit result
- self.__legend = None
-
- super(FitAction, self).__init__(
- plot, icon='math-fit', text='Fit curve',
- tooltip='Open a fit dialog',
- parent=parent)
-
- @property
- @deprecated(replacement='getXRange()[0]', since_version='0.13.0')
- def xmin(self):
- return self.getXRange()[0]
-
- @property
- @deprecated(replacement='getXRange()[1]', since_version='0.13.0')
- def xmax(self):
- return self.getXRange()[1]
-
- @property
- @deprecated(replacement='getXData()', since_version='0.13.0')
- def x(self):
- return self.getXData()
-
- @property
- @deprecated(replacement='getYData()', since_version='0.13.0')
- def y(self):
- return self.getYData()
-
- @property
- @deprecated(since_version='0.13.0')
- def xlabel(self):
- return self.__curveParams.get('xlabel', None)
-
- @property
- @deprecated(since_version='0.13.0')
- def ylabel(self):
- return self.__curveParams.get('ylabel', None)
-
- @property
- @deprecated(since_version='0.13.0')
- def legend(self):
- return self.__legend
-
- def _createToolWindow(self):
- # import done here rather than at module level to avoid circular import
- # FitWidget -> BackgroundWidget -> PlotWindow -> actions -> fit -> FitWidget
- from ...fit.FitWidget import FitWidget
-
- window = FitWidget(parent=self.plot)
- window.setWindowFlags(qt.Qt.Dialog)
- window.sigFitWidgetSignal.connect(self.handle_signal)
- return window
-
- def _connectPlot(self, window):
- if self.isXRangeUpdatedOnZoom():
- self.__setAutoXRangeEnabled(True)
- else:
- plot = self.plot
- if plot is None:
- _logger.error("No associated PlotWidget")
- return
- self._setXRange(*plot.getXAxis().getLimits())
-
- if self.isFittedItemUpdatedFromActiveCurve():
- self.__setFittedItemAutoUpdateEnabled(True)
- else:
- # Wait for the next iteration, else the plot is not yet initialized
- # No curve available
- qt.QTimer.singleShot(10, self._initFit)
-
- def _disconnectPlot(self, window):
- if self.isXRangeUpdatedOnZoom():
- self.__setAutoXRangeEnabled(False)
-
- if self.isFittedItemUpdatedFromActiveCurve():
- self.__setFittedItemAutoUpdateEnabled(False)
-
- def _initFit(self):
- plot = self.plot
- if plot is None:
- _logger.error("No associated PlotWidget")
- return
-
- item = _getUniqueCurveOrHistogram(plot)
- if item is None:
- # ambiguous case, we need to ask which plot item to fit
- isd = ItemsSelectionDialog(parent=plot, plot=plot)
- isd.setWindowTitle("Select item to be fitted")
- isd.setItemsSelectionMode(qt.QTableWidget.SingleSelection)
- isd.setAvailableKinds(["curve", "histogram"])
- isd.selectAllKinds()
-
- if not isd.exec_(): # Cancel
- self._getToolWindow().setVisible(False)
- else:
- selectedItems = isd.getSelectedItems()
- item = selectedItems[0] if len(selectedItems) == 1 else None
-
- self._setXRange(*plot.getXAxis().getLimits())
- self._setFittedItem(item)
-
- def __updateFitWidget(self):
- """Update the data/range used by the FitWidget"""
- fitWidget = self._getToolWindow()
-
- item = self._getFittedItem()
- xdata = self.getXData(copy=False)
- ydata = self.getYData(copy=False)
- if item is None or xdata is None or ydata is None:
- fitWidget.setData(y=None)
- fitWidget.setWindowTitle("No curve selected")
-
- else:
- xmin, xmax = self.getXRange()
- fitWidget.setData(
- xdata, ydata, xmin=xmin, xmax=xmax)
- fitWidget.setWindowTitle(
- "Fitting " + item.getName() +
- " on x range %f-%f" % (xmin, xmax))
-
- # X Range management
-
- def getXRange(self):
- """Returns the range on the X axis on which to perform the fit."""
- return self.__range
-
- def _setXRange(self, xmin, xmax):
- """Set the range on which the fit is done.
-
- :param float xmin:
- :param float xmax:
- """
- range_ = float(xmin), float(xmax)
- if self.__range != range_:
- self.__range = range_
- self.__updateFitWidget()
-
- def __setAutoXRangeEnabled(self, enabled):
- """Implement the change of update mode of the X range.
-
- :param bool enabled:
- """
- plot = self.plot
- if plot is None:
- _logger.error("No associated PlotWidget")
- return
-
- if enabled:
- self._setXRange(*plot.getXAxis().getLimits())
- plot.getXAxis().sigLimitsChanged.connect(self._setXRange)
- else:
- plot.getXAxis().sigLimitsChanged.disconnect(self._setXRange)
-
- def setXRangeUpdatedOnZoom(self, enabled):
- """Set whether or not to update the X range on zoom change.
-
- :param bool enabled:
- """
- if enabled != self.__rangeAutoUpdate:
- self.__rangeAutoUpdate = enabled
- if self._getToolWindow().isVisible():
- self.__setAutoXRangeEnabled(enabled)
-
- def isXRangeUpdatedOnZoom(self):
- """Returns the current mode of fitted data X range update.
-
- :rtype: bool
- """
- return self.__rangeAutoUpdate
-
- # Fitted item update
-
- def getXData(self, copy=True):
- """Returns the X data used for the fit or None if undefined.
-
- :param bool copy:
- True to get a copy of the data, False to get the internal data.
- :rtype: Union[numpy.ndarray,None]
- """
- return None if self.__x is None else numpy.array(self.__x, copy=copy)
-
- def getYData(self, copy=True):
- """Returns the Y data used for the fit or None if undefined.
-
- :param bool copy:
- True to get a copy of the data, False to get the internal data.
- :rtype: Union[numpy.ndarray,None]
- """
- return None if self.__y is None else numpy.array(self.__y, copy=copy)
-
- def _getFittedItem(self):
- """Returns the current item used for the fit
-
- :rtype: Union[~silx.gui.plot.items.Curve,~silx.gui.plot.items.Histogram,None]
- """
- return self.__item
-
- def _setFittedItem(self, item):
- """Set the curve to use for fitting.
-
- :param Union[~silx.gui.plot.items.Curve,~silx.gui.plot.items.Histogram,None] item:
- """
- plot = self.plot
- if plot is None:
- _logger.error("No associated PlotWidget")
-
- if plot is None or item is None:
- self.__item = None
- self.__curveParams = {}
- self.__updateFitWidget()
- return
-
- axis = item.getYAxis() if isinstance(item, items.YAxisMixIn) else 'left'
- self.__curveParams = {
- 'yaxis': axis,
- 'xlabel': plot.getXAxis().getLabel(),
- 'ylabel': plot.getYAxis(axis).getLabel(),
- }
- self.__legend = item.getName()
-
- if isinstance(item, items.Histogram):
- bin_edges = item.getBinEdgesData(copy=False)
- # take the middle coordinate between adjacent bin edges
- self.__x = (bin_edges[1:] + bin_edges[:-1]) / 2
- self.__y = item.getValueData(copy=False)
- # else take the active curve, or else the unique curve
- elif isinstance(item, items.Curve):
- self.__x = item.getXData(copy=False)
- self.__y = item.getYData(copy=False)
-
- self.__item = item
- self.__updateFitWidget()
-
- def __activeCurveChanged(self, previous, current):
- """Handle change of active curve in the PlotWidget
- """
- if current is None:
- self._setFittedItem(None)
- else:
- item = self.plot.getCurve(current)
- self._setFittedItem(item)
-
- def __setFittedItemAutoUpdateEnabled(self, enabled):
- """Implement the change of fitted item update mode
-
- :param bool enabled:
- """
- plot = self.plot
- if plot is None:
- _logger.error("No associated PlotWidget")
- return
-
- if enabled:
- self._setFittedItem(plot.getActiveCurve())
- plot.sigActiveCurveChanged.connect(self.__activeCurveChanged)
-
- else:
- plot.sigActiveCurveChanged.disconnect(
- self.__activeCurveChanged)
-
- def setFittedItemUpdatedFromActiveCurve(self, enabled):
- """Toggle fitted data synchronization with plot active curve.
-
- :param bool enabled:
- """
- enabled = bool(enabled)
- if enabled != self.__activeCurveSynchroEnabled:
- self.__activeCurveSynchroEnabled = enabled
- if self._getToolWindow().isVisible():
- self.__setFittedItemAutoUpdateEnabled(enabled)
-
- def isFittedItemUpdatedFromActiveCurve(self):
- """Returns True if fitted data is synchronized with plot.
-
- :rtype: bool
- """
- return self.__activeCurveSynchroEnabled
-
- # Handle fit completed
-
- def handle_signal(self, ddict):
- xdata = self.getXData(copy=False)
- if xdata is None:
- _logger.error("No reference data to display fit result for")
- return
-
- xmin, xmax = self.getXRange()
- x_fit = xdata[xmin <= xdata]
- x_fit = x_fit[x_fit <= xmax]
- fit_legend = "Fit <%s>" % self.__legend
- fit_curve = self.plot.getCurve(fit_legend)
-
- if ddict["event"] == "FitFinished":
- fit_widget = self._getToolWindow()
- if fit_widget is None:
- return
- y_fit = fit_widget.fitmanager.gendata()
- if fit_curve is None:
- self.plot.addCurve(x_fit, y_fit,
- fit_legend,
- resetzoom=False,
- **self.__curveParams)
- else:
- fit_curve.setData(x_fit, y_fit)
- fit_curve.setVisible(True)
- fit_curve.setYAxis(self.__curveParams.get('yaxis', 'left'))
-
- if ddict["event"] in ["FitStarted", "FitFailed"]:
- if fit_curve is not None:
- fit_curve.setVisible(False)
diff --git a/silx/gui/plot/actions/histogram.py b/silx/gui/plot/actions/histogram.py
deleted file mode 100644
index 0bba558..0000000
--- a/silx/gui/plot/actions/histogram.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-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.
-#
-# ###########################################################################*/
-"""
-:mod:`silx.gui.plot.actions.histogram` provides actions relative to histograms
-for :class:`.PlotWidget`.
-
-The following QAction are available:
-
-- :class:`PixelIntensitiesHistoAction`
-"""
-
-from __future__ import division
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__date__ = "01/12/2020"
-__license__ = "MIT"
-
-import numpy
-import logging
-import typing
-import weakref
-
-from .PlotToolAction import PlotToolAction
-
-from silx.math.histogram import Histogramnd
-from silx.math.combo import min_max
-from silx.gui import qt
-from silx.gui.plot import items
-from silx.gui.widgets.ElidedLabel import ElidedLabel
-from silx.utils.deprecation import deprecated
-
-_logger = logging.getLogger(__name__)
-
-
-class _ElidedLabel(ElidedLabel):
- """QLabel with a default size larger than what is displayed."""
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.setTextInteractionFlags(qt.Qt.TextSelectableByMouse)
-
- def sizeHint(self):
- hint = super().sizeHint()
- nbchar = max(len(self.getText()), 12)
- width = self.fontMetrics().boundingRect('#' * nbchar).width()
- return qt.QSize(max(hint.width(), width), hint.height())
-
-
-class _StatWidget(qt.QWidget):
- """Widget displaying a name and a value
-
- :param parent:
- :param name:
- """
-
- def __init__(self, parent=None, name: str=''):
- super().__init__(parent)
- layout = qt.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
-
- keyWidget = qt.QLabel(parent=self)
- keyWidget.setText("<b>" + name.capitalize() + ":<b>")
- layout.addWidget(keyWidget)
- self.__valueWidget = _ElidedLabel(parent=self)
- self.__valueWidget.setText("-")
- self.__valueWidget.setTextInteractionFlags(
- qt.Qt.TextSelectableByMouse | qt.Qt.TextSelectableByKeyboard)
- layout.addWidget(self.__valueWidget)
-
- def setValue(self, value: typing.Optional[float]):
- """Set the displayed value
-
- :param value:
- """
- self.__valueWidget.setText(
- "-" if value is None else "{:.5g}".format(value))
-
-
-class HistogramWidget(qt.QWidget):
- """Widget displaying a histogram and some statistic indicators"""
-
- _SUPPORTED_ITEM_CLASS = items.ImageBase, items.Scatter
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.setWindowTitle('Histogram')
-
- self.__itemRef = None # weakref on the item to track
-
- layout = qt.QVBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
-
- # Plot
- # Lazy import to avoid circular dependencies
- from silx.gui.plot.PlotWindow import Plot1D
- self.__plot = Plot1D(self)
- layout.addWidget(self.__plot)
-
- self.__plot.setDataMargins(0.1, 0.1, 0.1, 0.1)
- self.__plot.getXAxis().setLabel("Value")
- self.__plot.getYAxis().setLabel("Count")
- posInfo = self.__plot.getPositionInfoWidget()
- posInfo.setSnappingMode(posInfo.SNAPPING_CURVE)
-
- # Stats display
- statsWidget = qt.QWidget(self)
- layout.addWidget(statsWidget)
- statsLayout = qt.QHBoxLayout(statsWidget)
- statsLayout.setContentsMargins(4, 4, 4, 4)
-
- self.__statsWidgets = dict(
- (name, _StatWidget(parent=statsWidget, name=name))
- for name in ("min", "max", "mean", "std", "sum"))
-
- for widget in self.__statsWidgets.values():
- statsLayout.addWidget(widget)
- statsLayout.addStretch(1)
-
- def getPlotWidget(self):
- """Returns :class:`PlotWidget` use to display the histogram"""
- return self.__plot
-
- def resetZoom(self):
- """Reset PlotWidget zoom"""
- self.getPlotWidget().resetZoom()
-
- def reset(self):
- """Clear displayed information"""
- self.getPlotWidget().clear()
- self.setStatistics()
-
- def getItem(self) -> typing.Optional[items.Item]:
- """Returns item used to display histogram and statistics."""
- return None if self.__itemRef is None else self.__itemRef()
-
- def setItem(self, item: typing.Optional[items.Item]):
- """Set item from which to display histogram and statistics.
-
- :param item:
- """
- previous = self.getItem()
- if previous is not None:
- previous.sigItemChanged.disconnect(self.__itemChanged)
-
- self.__itemRef = None if item is None else weakref.ref(item)
- if item is not None:
- if isinstance(item, self._SUPPORTED_ITEM_CLASS):
- # Only listen signal for supported items
- item.sigItemChanged.connect(self.__itemChanged)
- self._updateFromItem()
-
- def __itemChanged(self, event):
- """Handle update of the item"""
- if event in (items.ItemChangedType.DATA, items.ItemChangedType.MASK):
- self._updateFromItem()
-
- def _updateFromItem(self):
- """Update histogram and stats from the item"""
- item = self.getItem()
-
- if item is None:
- self.reset()
- return
-
- if not isinstance(item, self._SUPPORTED_ITEM_CLASS):
- _logger.error("Unsupported item", item)
- self.reset()
- return
-
- # Compute histogram and stats
- array = item.getValueData(copy=False)
-
- if array.size == 0:
- self.reset()
- return
-
- xmin, xmax = min_max(array, min_positive=False, finite=True)
- nbins = min(1024, int(numpy.sqrt(array.size)))
- data_range = xmin, xmax
-
- # bad hack: get 256 bins in the case we have a B&W
- if numpy.issubdtype(array.dtype, numpy.integer):
- if nbins > xmax - xmin:
- nbins = xmax - xmin
-
- nbins = max(2, nbins)
-
- data = array.ravel().astype(numpy.float32)
- histogram = Histogramnd(data, n_bins=nbins, histo_range=data_range)
- if len(histogram.edges) != 1:
- _logger.error("Error while computing the histogram")
- self.reset()
- return
-
- self.setHistogram(histogram.histo, histogram.edges[0])
- self.resetZoom()
- self.setStatistics(
- min_=xmin,
- max_=xmax,
- mean=numpy.nanmean(array),
- std=numpy.nanstd(array),
- sum_=numpy.nansum(array))
-
- def setHistogram(self, histogram, edges):
- """Set displayed histogram
-
- :param histogram: Bin values (N)
- :param edges: Bin edges (N+1)
- """
- self.getPlotWidget().addHistogram(
- histogram=histogram,
- edges=edges,
- legend='histogram',
- fill=True,
- color='#66aad7',
- resetzoom=False)
-
- def getHistogram(self, copy: bool=True):
- """Returns currently displayed histogram.
-
- :param copy: True to get a copy,
- False to get internal representation (Do not modify!)
- :return: (histogram, edges) or None
- """
- for item in self.getPlotWidget().getItems():
- if item.getName() == 'histogram':
- return (item.getValueData(copy=copy),
- item.getBinEdgesData(copy=copy))
- else:
- return None
-
- def setStatistics(self,
- min_: typing.Optional[float] = None,
- max_: typing.Optional[float] = None,
- mean: typing.Optional[float] = None,
- std: typing.Optional[float] = None,
- sum_: typing.Optional[float] = None):
- """Set displayed statistic indicators."""
- self.__statsWidgets['min'].setValue(min_)
- self.__statsWidgets['max'].setValue(max_)
- self.__statsWidgets['mean'].setValue(mean)
- self.__statsWidgets['std'].setValue(std)
- self.__statsWidgets['sum'].setValue(sum_)
-
-
-class _LastActiveItem(qt.QObject):
-
- sigActiveItemChanged = qt.Signal(object, object)
- """Emitted when the active plot item have changed"""
-
- def __init__(self, parent, plot):
- assert plot is not None
- super(_LastActiveItem, self).__init__(parent=parent)
- self.__plot = weakref.ref(plot)
- self.__item = None
- item = self.__findActiveItem()
- self.setActiveItem(item)
- plot.sigActiveImageChanged.connect(self._activeImageChanged)
- plot.sigActiveScatterChanged.connect(self._activeScatterChanged)
-
- def getPlotWidget(self):
- return self.__plot()
-
- def __findActiveItem(self):
- plot = self.getPlotWidget()
- image = plot.getActiveImage()
- if image is not None:
- return image
- scatter = plot.getActiveScatter()
- if scatter is not None:
- return scatter
-
- def getActiveItem(self):
- if self.__item is None:
- return None
- item = self.__item()
- if item is None:
- self.__item = None
- return item
-
- def setActiveItem(self, item):
- previous = self.getActiveItem()
- if previous is item:
- return
- if item is None:
- self.__item = None
- else:
- self.__item = weakref.ref(item)
- self.sigActiveItemChanged.emit(previous, item)
-
- def _activeImageChanged(self, previous, current):
- """Handle active image change"""
- plot = self.getPlotWidget()
- if current is None: # Fall-back to active scatter if any
- self.setActiveItem(plot.getActiveScatter())
- else:
- item = plot.getImage(current)
- if item is None:
- self.setActiveItem(None)
- elif isinstance(item, items.ImageBase):
- self.setActiveItem(item)
- else:
- # Do not touch anything, which is consistent with silx v0.12 behavior
- pass
-
- def _activeScatterChanged(self, previous, current):
- """Handle active scatter change"""
- plot = self.getPlotWidget()
- if current is None: # Fall-back to active image if any
- self.setActiveItem(plot.getActiveImage())
- else:
- item = plot.getScatter(current)
- self.setActiveItem(item)
-
-
-class PixelIntensitiesHistoAction(PlotToolAction):
- """QAction to plot the pixels intensities diagram
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- PlotToolAction.__init__(self,
- plot,
- icon='pixel-intensities',
- text='pixels intensity',
- tooltip='Compute image intensity distribution',
- parent=parent)
- self._lastItemFilter = _LastActiveItem(self, plot)
-
- def _connectPlot(self, window):
- self._lastItemFilter.sigActiveItemChanged.connect(self._activeItemChanged)
- item = self._lastItemFilter.getActiveItem()
- self.getHistogramWidget().setItem(item)
- PlotToolAction._connectPlot(self, window)
-
- def _disconnectPlot(self, window):
- self._lastItemFilter.sigActiveItemChanged.disconnect(self._activeItemChanged)
- PlotToolAction._disconnectPlot(self, window)
- self.getHistogramWidget().setItem(None)
-
- def _activeItemChanged(self, previous, current):
- if self._isWindowInUse():
- self.getHistogramWidget().setItem(current)
-
- @deprecated(since_version='0.15.0')
- def computeIntensityDistribution(self):
- self.getHistogramWidget()._updateFromItem()
-
- def getHistogramWidget(self):
- """Returns the widget displaying the histogram"""
- return self._getToolWindow()
-
- @deprecated(since_version='0.15.0',
- replacement='getHistogramWidget().getPlotWidget()')
- def getHistogramPlotWidget(self):
- return self._getToolWindow().getPlotWidget()
-
- def _createToolWindow(self):
- return HistogramWidget(self.plot, qt.Qt.Window)
-
- def getHistogram(self) -> typing.Optional[numpy.ndarray]:
- """Return the last computed histogram
-
- :return: the histogram displayed in the HistogramWidget
- """
- histogram = self.getHistogramWidget().getHistogram()
- return None if histogram is None else histogram[0]
diff --git a/silx/gui/plot/actions/io.py b/silx/gui/plot/actions/io.py
deleted file mode 100644
index f728b7a..0000000
--- a/silx/gui/plot/actions/io.py
+++ /dev/null
@@ -1,818 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2020 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.
-#
-# ###########################################################################*/
-"""
-:mod:`silx.gui.plot.actions.io` provides a set of QAction relative of inputs
-and outputs for a :class:`.PlotWidget`.
-
-The following QAction are available:
-
-- :class:`CopyAction`
-- :class:`PrintAction`
-- :class:`SaveAction`
-"""
-
-from __future__ import division
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "25/09/2020"
-
-from . import PlotAction
-from silx.io.utils import save1D, savespec, NEXUS_HDF5_EXT
-from silx.io.nxdata import save_NXdata
-import logging
-import sys
-import os.path
-from collections import OrderedDict
-import traceback
-import numpy
-from silx.utils.deprecation import deprecated
-from silx.gui import qt, printer
-from silx.gui.dialog.GroupDialog import GroupDialog
-from silx.third_party.EdfFile import EdfFile
-from silx.third_party.TiffIO import TiffIO
-from ...utils.image import convertArrayToQImage
-if sys.version_info[0] == 3:
- from io import BytesIO
-else:
- import cStringIO as _StringIO
- BytesIO = _StringIO.StringIO
-
-_logger = logging.getLogger(__name__)
-
-_NEXUS_HDF5_EXT_STR = ' '.join(['*' + ext for ext in NEXUS_HDF5_EXT])
-
-
-def selectOutputGroup(h5filename):
- """Open a dialog to prompt the user to select a group in
- which to output data.
-
- :param str h5filename: name of an existing HDF5 file
- :rtype: str
- :return: Name of output group, or None if the dialog was cancelled
- """
- dialog = GroupDialog()
- dialog.addFile(h5filename)
- dialog.setWindowTitle("Select an output group")
- if not dialog.exec_():
- return None
- return dialog.getSelectedDataUrl().data_path()
-
-
-class SaveAction(PlotAction):
- """QAction for saving Plot content.
-
- It opens a Save as... dialog.
-
- :param plot: :class:`.PlotWidget` instance on which to operate.
- :param parent: See :class:`QAction`.
- """
-
- SNAPSHOT_FILTER_SVG = 'Plot Snapshot as SVG (*.svg)'
- SNAPSHOT_FILTER_PNG = 'Plot Snapshot as PNG (*.png)'
-
- DEFAULT_ALL_FILTERS = (SNAPSHOT_FILTER_PNG, SNAPSHOT_FILTER_SVG)
-
- # Dict of curve filters with CSV-like format
- # Using ordered dict to guarantee filters order
- # Note: '%.18e' is numpy.savetxt default format
- CURVE_FILTERS_TXT = OrderedDict((
- ('Curve as Raw ASCII (*.txt)',
- {'fmt': '%.18e', 'delimiter': ' ', 'header': False}),
- ('Curve as ";"-separated CSV (*.csv)',
- {'fmt': '%.18e', 'delimiter': ';', 'header': True}),
- ('Curve as ","-separated CSV (*.csv)',
- {'fmt': '%.18e', 'delimiter': ',', 'header': True}),
- ('Curve as tab-separated CSV (*.csv)',
- {'fmt': '%.18e', 'delimiter': '\t', 'header': True}),
- ('Curve as OMNIC CSV (*.csv)',
- {'fmt': '%.7E', 'delimiter': ',', 'header': False}),
- ('Curve as SpecFile (*.dat)',
- {'fmt': '%.10g', 'delimiter': '', 'header': False})
- ))
-
- CURVE_FILTER_NPY = 'Curve as NumPy binary file (*.npy)'
-
- CURVE_FILTER_NXDATA = 'Curve as NXdata (%s)' % _NEXUS_HDF5_EXT_STR
-
- DEFAULT_CURVE_FILTERS = list(CURVE_FILTERS_TXT.keys()) + [
- CURVE_FILTER_NPY, CURVE_FILTER_NXDATA]
-
- DEFAULT_ALL_CURVES_FILTERS = ("All curves as SpecFile (*.dat)",)
-
- IMAGE_FILTER_EDF = 'Image data as EDF (*.edf)'
- IMAGE_FILTER_TIFF = 'Image data as TIFF (*.tif)'
- IMAGE_FILTER_NUMPY = 'Image data as NumPy binary file (*.npy)'
- IMAGE_FILTER_ASCII = 'Image data as ASCII (*.dat)'
- IMAGE_FILTER_CSV_COMMA = 'Image data as ,-separated CSV (*.csv)'
- IMAGE_FILTER_CSV_SEMICOLON = 'Image data as ;-separated CSV (*.csv)'
- IMAGE_FILTER_CSV_TAB = 'Image data as tab-separated CSV (*.csv)'
- IMAGE_FILTER_RGB_PNG = 'Image as PNG (*.png)'
- IMAGE_FILTER_NXDATA = 'Image as NXdata (%s)' % _NEXUS_HDF5_EXT_STR
-
- DEFAULT_IMAGE_FILTERS = (IMAGE_FILTER_EDF,
- IMAGE_FILTER_TIFF,
- IMAGE_FILTER_NUMPY,
- IMAGE_FILTER_ASCII,
- IMAGE_FILTER_CSV_COMMA,
- IMAGE_FILTER_CSV_SEMICOLON,
- IMAGE_FILTER_CSV_TAB,
- IMAGE_FILTER_RGB_PNG,
- IMAGE_FILTER_NXDATA)
-
- SCATTER_FILTER_NXDATA = 'Scatter as NXdata (%s)' % _NEXUS_HDF5_EXT_STR
- DEFAULT_SCATTER_FILTERS = (SCATTER_FILTER_NXDATA,)
-
- # filters for which we don't want an "overwrite existing file" warning
- DEFAULT_APPEND_FILTERS = (CURVE_FILTER_NXDATA, IMAGE_FILTER_NXDATA,
- SCATTER_FILTER_NXDATA)
-
- def __init__(self, plot, parent=None):
- self._filters = {
- 'all': OrderedDict(),
- 'curve': OrderedDict(),
- 'curves': OrderedDict(),
- 'image': OrderedDict(),
- 'scatter': OrderedDict()}
-
- self._appendFilters = list(self.DEFAULT_APPEND_FILTERS)
-
- # Initialize filters
- for nameFilter in self.DEFAULT_ALL_FILTERS:
- self.setFileFilter(
- dataKind='all', nameFilter=nameFilter, func=self._saveSnapshot)
-
- for nameFilter in self.DEFAULT_CURVE_FILTERS:
- self.setFileFilter(
- dataKind='curve', nameFilter=nameFilter, func=self._saveCurve)
-
- for nameFilter in self.DEFAULT_ALL_CURVES_FILTERS:
- self.setFileFilter(
- dataKind='curves', nameFilter=nameFilter, func=self._saveCurves)
-
- for nameFilter in self.DEFAULT_IMAGE_FILTERS:
- self.setFileFilter(
- dataKind='image', nameFilter=nameFilter, func=self._saveImage)
-
- for nameFilter in self.DEFAULT_SCATTER_FILTERS:
- self.setFileFilter(
- dataKind='scatter', nameFilter=nameFilter, func=self._saveScatter)
-
- super(SaveAction, self).__init__(
- plot, icon='document-save', text='Save as...',
- tooltip='Save curve/image/plot snapshot dialog',
- triggered=self._actionTriggered,
- checkable=False, parent=parent)
- self.setShortcut(qt.QKeySequence.Save)
- self.setShortcutContext(qt.Qt.WidgetShortcut)
-
- @staticmethod
- def _errorMessage(informativeText='', parent=None):
- """Display an error message."""
- # TODO issue with QMessageBox size fixed and too small
- msg = qt.QMessageBox(parent)
- msg.setIcon(qt.QMessageBox.Critical)
- msg.setInformativeText(informativeText + ' ' + str(sys.exc_info()[1]))
- msg.setDetailedText(traceback.format_exc())
- msg.exec_()
-
- def _saveSnapshot(self, plot, filename, nameFilter):
- """Save a snapshot of the :class:`PlotWindow` widget.
-
- :param str filename: The name of the file to write
- :param str nameFilter: The selected name filter
- :return: False if format is not supported or save failed,
- True otherwise.
- """
- if nameFilter == self.SNAPSHOT_FILTER_PNG:
- fileFormat = 'png'
- elif nameFilter == self.SNAPSHOT_FILTER_SVG:
- fileFormat = 'svg'
- else: # Format not supported
- _logger.error(
- 'Saving plot snapshot failed: format not supported')
- return False
-
- plot.saveGraph(filename, fileFormat=fileFormat)
- return True
-
- def _getAxesLabels(self, item):
- # If curve has no associated label, get the default from the plot
- xlabel = item.getXLabel() or self.plot.getXAxis().getLabel()
- ylabel = item.getYLabel() or self.plot.getYAxis().getLabel()
- return xlabel, ylabel
-
- def _get1dData(self, item):
- "provide xdata, [ydata], xlabel, [ylabel] and manages error bars"
- xlabel, ylabel = self._getAxesLabels(item)
- x_data = item.getXData(copy=False)
- y_data = item.getYData(copy=False)
- x_err = item.getXErrorData(copy=False)
- y_err = item.getYErrorData(copy=False)
- labels = [ylabel]
- data = [y_data]
-
- if x_err is not None:
- if numpy.isscalar(x_err):
- data.append(numpy.zeros_like(y_data) + x_err)
- labels.append(xlabel + "_errors")
- elif x_err.ndim == 1:
- data.append(x_err)
- labels.append(xlabel + "_errors")
- elif x_err.ndim == 2:
- data.append(x_err[0])
- labels.append(xlabel + "_errors_below")
- data.append(x_err[1])
- labels.append(xlabel + "_errors_above")
-
- if y_err is not None:
- if numpy.isscalar(y_err):
- data.append(numpy.zeros_like(y_data) + y_err)
- labels.append(ylabel + "_errors")
- elif y_err.ndim == 1:
- data.append(y_err)
- labels.append(ylabel + "_errors")
- elif y_err.ndim == 2:
- data.append(y_err[0])
- labels.append(ylabel + "_errors_below")
- data.append(y_err[1])
- labels.append(ylabel + "_errors_above")
- return x_data, data, xlabel, labels
-
- @staticmethod
- def _selectWriteableOutputGroup(filename, parent):
- if os.path.exists(filename) and os.path.isfile(filename) \
- and os.access(filename, os.W_OK):
- entryPath = selectOutputGroup(filename)
- if entryPath is None:
- _logger.info("Save operation cancelled")
- return None
- return entryPath
- elif not os.path.exists(filename):
- # create new entry in new file
- return "/entry"
- else:
- SaveAction._errorMessage('Save failed (file access issue)\n', parent=parent)
- return None
-
- def _saveCurveAsNXdata(self, curve, filename):
- entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
- if entryPath is None:
- return False
-
- xlabel, ylabel = self._getAxesLabels(curve)
-
- return save_NXdata(
- filename,
- nxentry_name=entryPath,
- signal=curve.getYData(copy=False),
- axes=[curve.getXData(copy=False)],
- signal_name="y",
- axes_names=["x"],
- signal_long_name=ylabel,
- axes_long_names=[xlabel],
- signal_errors=curve.getYErrorData(copy=False),
- axes_errors=[curve.getXErrorData(copy=True)],
- title=self.plot.getGraphTitle())
-
- def _saveCurve(self, plot, filename, nameFilter):
- """Save a curve from the plot.
-
- :param str filename: The name of the file to write
- :param str nameFilter: The selected name filter
- :return: False if format is not supported or save failed,
- True otherwise.
- """
- if nameFilter not in self.DEFAULT_CURVE_FILTERS:
- return False
-
- # Check if a curve is to be saved
- curve = plot.getActiveCurve()
- # before calling _saveCurve, if there is no selected curve, we
- # make sure there is only one curve on the graph
- if curve is None:
- curves = plot.getAllCurves()
- if not curves:
- self._errorMessage("No curve to be saved", parent=self.plot)
- return False
- curve = curves[0]
-
- if nameFilter in self.CURVE_FILTERS_TXT:
- filter_ = self.CURVE_FILTERS_TXT[nameFilter]
- fmt = filter_['fmt']
- csvdelim = filter_['delimiter']
- autoheader = filter_['header']
- else:
- # .npy or nxdata
- fmt, csvdelim, autoheader = ("", "", False)
-
- if nameFilter == self.CURVE_FILTER_NXDATA:
- return self._saveCurveAsNXdata(curve, filename)
-
- xdata, data, xlabel, labels = self._get1dData(curve)
-
- try:
- save1D(filename,
- xdata, data,
- xlabel, labels,
- fmt=fmt, csvdelim=csvdelim,
- autoheader=autoheader)
- except IOError:
- self._errorMessage('Save failed\n', parent=self.plot)
- return False
-
- return True
-
- def _saveCurves(self, plot, filename, nameFilter):
- """Save all curves from the plot.
-
- :param str filename: The name of the file to write
- :param str nameFilter: The selected name filter
- :return: False if format is not supported or save failed,
- True otherwise.
- """
- if nameFilter not in self.DEFAULT_ALL_CURVES_FILTERS:
- return False
-
- curves = plot.getAllCurves()
- if not curves:
- self._errorMessage("No curves to be saved", parent=self.plot)
- return False
-
- curve = curves[0]
- scanno = 1
- try:
- xdata, data, xlabel, labels = self._get1dData(curve)
-
- specfile = savespec(filename,
- xdata, data,
- xlabel, labels,
- fmt="%.7g", scan_number=1, mode="w",
- write_file_header=True,
- close_file=False)
- except IOError:
- self._errorMessage('Save failed\n', parent=self.plot)
- return False
-
- for curve in curves[1:]:
- try:
- scanno += 1
- xdata, data, xlabel, labels = self._get1dData(curve)
- specfile = savespec(specfile,
- xdata, data,
- xlabel, labels,
- fmt="%.7g", scan_number=scanno,
- write_file_header=False,
- close_file=False)
- except IOError:
- self._errorMessage('Save failed\n', parent=self.plot)
- return False
- specfile.close()
-
- return True
-
- def _saveImage(self, plot, filename, nameFilter):
- """Save an image from the plot.
-
- :param str filename: The name of the file to write
- :param str nameFilter: The selected name filter
- :return: False if format is not supported or save failed,
- True otherwise.
- """
- if nameFilter not in self.DEFAULT_IMAGE_FILTERS:
- return False
-
- image = plot.getActiveImage()
- if image is None:
- qt.QMessageBox.warning(
- plot, "No Data", "No image to be saved")
- return False
-
- data = image.getData(copy=False)
-
- # TODO Use silx.io for writing files
- if nameFilter == self.IMAGE_FILTER_EDF:
- edfFile = EdfFile(filename, access="w+")
- edfFile.WriteImage({}, data, Append=0)
- return True
-
- elif nameFilter == self.IMAGE_FILTER_TIFF:
- tiffFile = TiffIO(filename, mode='w')
- tiffFile.writeImage(data, software='silx')
- return True
-
- elif nameFilter == self.IMAGE_FILTER_NUMPY:
- try:
- numpy.save(filename, data)
- except IOError:
- self._errorMessage('Save failed\n', parent=self.plot)
- return False
- return True
-
- elif nameFilter == self.IMAGE_FILTER_NXDATA:
- entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
- if entryPath is None:
- return False
- xorigin, yorigin = image.getOrigin()
- xscale, yscale = image.getScale()
- xaxis = xorigin + xscale * numpy.arange(data.shape[1])
- yaxis = yorigin + yscale * numpy.arange(data.shape[0])
- xlabel, ylabel = self._getAxesLabels(image)
- interpretation = "image" if len(data.shape) == 2 else "rgba-image"
-
- return save_NXdata(filename,
- nxentry_name=entryPath,
- signal=data,
- axes=[yaxis, xaxis],
- signal_name="image",
- axes_names=["y", "x"],
- axes_long_names=[ylabel, xlabel],
- title=plot.getGraphTitle(),
- interpretation=interpretation)
-
- elif nameFilter in (self.IMAGE_FILTER_ASCII,
- self.IMAGE_FILTER_CSV_COMMA,
- self.IMAGE_FILTER_CSV_SEMICOLON,
- self.IMAGE_FILTER_CSV_TAB):
- csvdelim, filetype = {
- self.IMAGE_FILTER_ASCII: (' ', 'txt'),
- self.IMAGE_FILTER_CSV_COMMA: (',', 'csv'),
- self.IMAGE_FILTER_CSV_SEMICOLON: (';', 'csv'),
- self.IMAGE_FILTER_CSV_TAB: ('\t', 'csv'),
- }[nameFilter]
-
- height, width = data.shape
- rows, cols = numpy.mgrid[0:height, 0:width]
- try:
- save1D(filename, rows.ravel(), (cols.ravel(), data.ravel()),
- filetype=filetype,
- xlabel='row',
- ylabels=['column', 'value'],
- csvdelim=csvdelim,
- autoheader=True)
-
- except IOError:
- self._errorMessage('Save failed\n', parent=self.plot)
- return False
- return True
-
- elif nameFilter == self.IMAGE_FILTER_RGB_PNG:
- # Get displayed image
- rgbaImage = image.getRgbaImageData(copy=False)
- # Convert RGB QImage
- qimage = convertArrayToQImage(rgbaImage[:, :, :3])
-
- if qimage.save(filename, 'PNG'):
- return True
- else:
- _logger.error('Failed to save image as %s', filename)
- qt.QMessageBox.critical(
- self.parent(),
- 'Save image as',
- 'Failed to save image')
-
- return False
-
- def _saveScatter(self, plot, filename, nameFilter):
- """Save an image from the plot.
-
- :param str filename: The name of the file to write
- :param str nameFilter: The selected name filter
- :return: False if format is not supported or save failed,
- True otherwise.
- """
- if nameFilter not in self.DEFAULT_SCATTER_FILTERS:
- return False
-
- if nameFilter == self.SCATTER_FILTER_NXDATA:
- entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
- if entryPath is None:
- return False
- scatter = plot.getScatter()
-
- x = scatter.getXData(copy=False)
- y = scatter.getYData(copy=False)
- z = scatter.getValueData(copy=False)
-
- xerror = scatter.getXErrorData(copy=False)
- if isinstance(xerror, float):
- xerror = xerror * numpy.ones(x.shape, dtype=numpy.float32)
-
- yerror = scatter.getYErrorData(copy=False)
- if isinstance(yerror, float):
- yerror = yerror * numpy.ones(x.shape, dtype=numpy.float32)
-
- xlabel = plot.getGraphXLabel()
- ylabel = plot.getGraphYLabel()
-
- return save_NXdata(
- filename,
- nxentry_name=entryPath,
- signal=z,
- axes=[x, y],
- signal_name="values",
- axes_names=["x", "y"],
- axes_long_names=[xlabel, ylabel],
- axes_errors=[xerror, yerror],
- title=plot.getGraphTitle())
-
- def setFileFilter(self, dataKind, nameFilter, func, index=None, appendToFile=False):
- """Set a name filter to add/replace a file format support
-
- :param str dataKind:
- The kind of data for which the provided filter is valid.
- One of: 'all', 'curve', 'curves', 'image', 'scatter'
- :param str nameFilter: The name filter in the QFileDialog.
- See :meth:`QFileDialog.setNameFilters`.
- :param callable func: The function to call to perform saving.
- Expected signature is:
- bool func(PlotWidget plot, str filename, str nameFilter)
- :param bool appendToFile: True to append the data into the selected
- file.
- :param integer index: Index of the filter in the final list (or None)
- """
- assert dataKind in ('all', 'curve', 'curves', 'image', 'scatter')
-
- if appendToFile:
- self._appendFilters.append(nameFilter)
-
- # first append or replace the new filter to prevent colissions
- self._filters[dataKind][nameFilter] = func
- if index is None:
- # we are already done
- return
-
- # get the current ordered list of keys
- keyList = list(self._filters[dataKind].keys())
-
- # deal with negative indices
- if index < 0:
- index = len(keyList) + index
- if index < 0:
- index = 0
-
- if index >= len(keyList):
- # nothing to be done, already at the end
- txt = 'Requested index %d impossible, already at the end' % index
- _logger.info(txt)
- return
-
- # get the new ordered list
- oldIndex = keyList.index(nameFilter)
- del keyList[oldIndex]
- keyList.insert(index, nameFilter)
-
- # build the new filters
- newFilters = OrderedDict()
- for key in keyList:
- newFilters[key] = self._filters[dataKind][key]
-
- # and update the filters
- self._filters[dataKind] = newFilters
- return
-
- def getFileFilters(self, dataKind):
- """Returns the nameFilter and associated function for a kind of data.
-
- :param str dataKind:
- The kind of data for which the provided filter is valid.
- On of: 'all', 'curve', 'curves', 'image', 'scatter'
- :return: {nameFilter: function} associations.
- :rtype: collections.OrderedDict
- """
- assert dataKind in ('all', 'curve', 'curves', 'image', 'scatter')
-
- return self._filters[dataKind].copy()
-
- def _actionTriggered(self, checked=False):
- """Handle save action."""
- # Set-up filters
- filters = OrderedDict()
-
- # Add image filters if there is an active image
- if self.plot.getActiveImage() is not None:
- filters.update(self._filters['image'].items())
-
- # Add curve filters if there is a curve to save
- if (self.plot.getActiveCurve() is not None or
- len(self.plot.getAllCurves()) == 1):
- filters.update(self._filters['curve'].items())
- if len(self.plot.getAllCurves()) >= 1:
- filters.update(self._filters['curves'].items())
-
- # Add scatter filters if there is a scatter
- # todo: CSV
- if self.plot.getScatter() is not None:
- filters.update(self._filters['scatter'].items())
-
- filters.update(self._filters['all'].items())
-
- # Create and run File dialog
- dialog = qt.QFileDialog(self.plot)
- dialog.setOption(dialog.DontUseNativeDialog)
- dialog.setWindowTitle("Output File Selection")
- dialog.setModal(1)
- dialog.setNameFilters(list(filters.keys()))
-
- dialog.setFileMode(dialog.AnyFile)
- dialog.setAcceptMode(dialog.AcceptSave)
-
- def onFilterSelection(filt_):
- # disable overwrite confirmation for NXdata types,
- # because we append the data to existing files
- if filt_ in self._appendFilters:
- dialog.setOption(dialog.DontConfirmOverwrite)
- else:
- dialog.setOption(dialog.DontConfirmOverwrite, False)
-
- dialog.filterSelected.connect(onFilterSelection)
-
- if not dialog.exec_():
- return False
-
- nameFilter = dialog.selectedNameFilter()
- filename = dialog.selectedFiles()[0]
- dialog.close()
-
- if '(' in nameFilter and ')' == nameFilter.strip()[-1]:
- # Check for correct file extension
- # Extract file extensions as .something
- extensions = [ext[ext.find('.'):] for ext in
- nameFilter[nameFilter.find('(') + 1:-1].split()]
- for ext in extensions:
- if (len(filename) > len(ext) and
- filename[-len(ext):].lower() == ext.lower()):
- break
- else: # filename has no extension supported in nameFilter, add one
- if len(extensions) >= 1:
- filename += extensions[0]
-
- # Handle save
- func = filters.get(nameFilter, None)
- if func is not None:
- return func(self.plot, filename, nameFilter)
- else:
- _logger.error('Unsupported file filter: %s', nameFilter)
- return False
-
-
-def _plotAsPNG(plot):
- """Save a :class:`Plot` as PNG and return the payload.
-
- :param plot: The :class:`Plot` to save
- """
- pngFile = BytesIO()
- plot.saveGraph(pngFile, fileFormat='png')
- pngFile.flush()
- pngFile.seek(0)
- data = pngFile.read()
- pngFile.close()
- return data
-
-
-class PrintAction(PlotAction):
- """QAction for printing the plot.
-
- It opens a Print dialog.
-
- Current implementation print a bitmap of the plot area and not vector
- graphics, so printing quality is not great.
-
- :param plot: :class:`.PlotWidget` instance on which to operate.
- :param parent: See :class:`QAction`.
- """
-
- def __init__(self, plot, parent=None):
- super(PrintAction, self).__init__(
- plot, icon='document-print', text='Print...',
- tooltip='Open print dialog',
- triggered=self.printPlot,
- checkable=False, parent=parent)
- self.setShortcut(qt.QKeySequence.Print)
- self.setShortcutContext(qt.Qt.WidgetShortcut)
-
- def getPrinter(self):
- """The QPrinter instance used by the PrintAction.
-
- :rtype: QPrinter
- """
- return printer.getDefaultPrinter()
-
- @property
- @deprecated(replacement="getPrinter()", since_version="0.8.0")
- def printer(self):
- return self.getPrinter()
-
- def printPlotAsWidget(self):
- """Open the print dialog and print the plot.
-
- Use :meth:`QWidget.render` to print the plot
-
- :return: True if successful
- """
- dialog = qt.QPrintDialog(self.getPrinter(), self.plot)
- dialog.setWindowTitle('Print Plot')
- if not dialog.exec_():
- return False
-
- # Print a snapshot of the plot widget at the top of the page
- widget = self.plot.centralWidget()
-
- painter = qt.QPainter()
- if not painter.begin(self.getPrinter()):
- return False
-
- pageRect = self.getPrinter().pageRect()
- xScale = pageRect.width() / widget.width()
- yScale = pageRect.height() / widget.height()
- scale = min(xScale, yScale)
-
- painter.translate(pageRect.width() / 2., 0.)
- painter.scale(scale, scale)
- painter.translate(-widget.width() / 2., 0.)
- widget.render(painter)
- painter.end()
-
- return True
-
- def printPlot(self):
- """Open the print dialog and print the plot.
-
- Use :meth:`Plot.saveGraph` to print the plot.
-
- :return: True if successful
- """
- # Init printer and start printer dialog
- dialog = qt.QPrintDialog(self.getPrinter(), self.plot)
- dialog.setWindowTitle('Print Plot')
- if not dialog.exec_():
- return False
-
- # Save Plot as PNG and make a pixmap from it with default dpi
- pngData = _plotAsPNG(self.plot)
-
- pixmap = qt.QPixmap()
- pixmap.loadFromData(pngData, 'png')
-
- xScale = self.getPrinter().pageRect().width() / pixmap.width()
- yScale = self.getPrinter().pageRect().height() / pixmap.height()
- scale = min(xScale, yScale)
-
- # Draw pixmap with painter
- painter = qt.QPainter()
- if not painter.begin(self.getPrinter()):
- return False
-
- painter.drawPixmap(0, 0,
- pixmap.width() * scale,
- pixmap.height() * scale,
- pixmap)
- painter.end()
-
- return True
-
-
-class CopyAction(PlotAction):
- """QAction to copy :class:`.PlotWidget` content to clipboard.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(CopyAction, self).__init__(
- plot, icon='edit-copy', text='Copy plot',
- tooltip='Copy a snapshot of the plot into the clipboard',
- triggered=self.copyPlot,
- checkable=False, parent=parent)
- self.setShortcut(qt.QKeySequence.Copy)
- self.setShortcutContext(qt.Qt.WidgetShortcut)
-
- def copyPlot(self):
- """Copy plot content to the clipboard as a bitmap."""
- # Save Plot as PNG and make a QImage from it with default dpi
- pngData = _plotAsPNG(self.plot)
- image = qt.QImage.fromData(pngData, 'png')
- qt.QApplication.clipboard().setImage(image)
diff --git a/silx/gui/plot/actions/medfilt.py b/silx/gui/plot/actions/medfilt.py
deleted file mode 100644
index f86a377..0000000
--- a/silx/gui/plot/actions/medfilt.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2020 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.
-#
-# ###########################################################################*/
-"""
-:mod:`silx.gui.plot.actions.medfilt` provides a set of QAction to apply filter
-on data contained in a :class:`.PlotWidget`.
-
-The following QAction are available:
-
-- :class:`MedianFilterAction`
-- :class:`MedianFilter1DAction`
-- :class:`MedianFilter2DAction`
-
-"""
-
-from __future__ import division
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__license__ = "MIT"
-
-__date__ = "10/10/2018"
-
-from .PlotToolAction import PlotToolAction
-from silx.gui.widgets.MedianFilterDialog import MedianFilterDialog
-from silx.math.medianfilter import medfilt2d
-import logging
-
-_logger = logging.getLogger(__name__)
-
-
-class MedianFilterAction(PlotToolAction):
- """QAction to plot the pixels intensities diagram
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- PlotToolAction.__init__(self,
- plot,
- icon='median-filter',
- text='median filter',
- tooltip='Apply a median filter on the image',
- parent=parent)
- self._originalImage = None
- self._legend = None
- self._filteredImage = None
-
- def _createToolWindow(self):
- popup = MedianFilterDialog(parent=self.plot)
- popup.sigFilterOptChanged.connect(self._updateFilter)
- return popup
-
- def _connectPlot(self, window):
- PlotToolAction._connectPlot(self, window)
- self.plot.sigActiveImageChanged.connect(self._updateActiveImage)
- self._updateActiveImage()
-
- def _disconnectPlot(self, window):
- PlotToolAction._disconnectPlot(self, window)
- self.plot.sigActiveImageChanged.disconnect(self._updateActiveImage)
-
- def _updateActiveImage(self):
- """Set _activeImageLegend and _originalImage from the active image"""
- self._activeImageLegend = self.plot.getActiveImage(just_legend=True)
- if self._activeImageLegend is None:
- self._originalImage = None
- self._legend = None
- else:
- self._originalImage = self.plot.getImage(self._activeImageLegend).getData(copy=False)
- self._legend = self.plot.getImage(self._activeImageLegend).getName()
-
- def _updateFilter(self, kernelWidth, conditional=False):
- if self._originalImage is None:
- return
-
- self.plot.sigActiveImageChanged.disconnect(self._updateActiveImage)
- filteredImage = self._computeFilteredImage(kernelWidth, conditional)
- self.plot.addImage(data=filteredImage,
- legend=self._legend,
- replace=True)
- self.plot.sigActiveImageChanged.connect(self._updateActiveImage)
-
- def _computeFilteredImage(self, kernelWidth, conditional):
- raise NotImplementedError('MedianFilterAction is a an abstract class')
-
- def getFilteredImage(self):
- """
- :return: the image with the median filter apply on"""
- return self._filteredImage
-
-
-class MedianFilter1DAction(MedianFilterAction):
- """Define the MedianFilterAction for 1D
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
- def __init__(self, plot, parent=None):
- MedianFilterAction.__init__(self,
- plot,
- parent=parent)
-
- def _computeFilteredImage(self, kernelWidth, conditional):
- assert(self.plot is not None)
- return medfilt2d(self._originalImage,
- (kernelWidth, 1),
- conditional)
-
-
-class MedianFilter2DAction(MedianFilterAction):
- """Define the MedianFilterAction for 2D
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
- def __init__(self, plot, parent=None):
- MedianFilterAction.__init__(self,
- plot,
- parent=parent)
-
- def _computeFilteredImage(self, kernelWidth, conditional):
- assert(self.plot is not None)
- return medfilt2d(self._originalImage,
- (kernelWidth, kernelWidth),
- conditional)
diff --git a/silx/gui/plot/actions/mode.py b/silx/gui/plot/actions/mode.py
deleted file mode 100644
index ee05256..0000000
--- a/silx/gui/plot/actions/mode.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2018 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.
-#
-# ###########################################################################*/
-"""
-:mod:`silx.gui.plot.actions.mode` provides a set of QAction relative to mouse
-mode of a :class:`.PlotWidget`.
-
-The following QAction are available:
-
-- :class:`ZoomModeAction`
-- :class:`PanModeAction`
-"""
-
-from __future__ import division
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "16/08/2017"
-
-from . import PlotAction
-import logging
-
-_logger = logging.getLogger(__name__)
-
-
-class ZoomModeAction(PlotAction):
- """QAction controlling the zoom mode of a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(ZoomModeAction, self).__init__(
- plot, icon='zoom', text='Zoom mode',
- tooltip='Zoom in or out',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- # Listen to mode change
- self.plot.sigInteractiveModeChanged.connect(self._modeChanged)
- # Init the state
- self._modeChanged(None)
-
- def _modeChanged(self, source):
- modeDict = self.plot.getInteractiveMode()
- old = self.blockSignals(True)
- self.setChecked(modeDict["mode"] == "zoom")
- self.blockSignals(old)
-
- def _actionTriggered(self, checked=False):
- plot = self.plot
- if plot is not None:
- plot.setInteractiveMode('zoom', source=self)
-
-
-class PanModeAction(PlotAction):
- """QAction controlling the pan mode of a :class:`.PlotWidget`.
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- super(PanModeAction, self).__init__(
- plot, icon='pan', text='Pan mode',
- tooltip='Pan the view',
- triggered=self._actionTriggered,
- checkable=True, parent=parent)
- # Listen to mode change
- self.plot.sigInteractiveModeChanged.connect(self._modeChanged)
- # Init the state
- self._modeChanged(None)
-
- def _modeChanged(self, source):
- modeDict = self.plot.getInteractiveMode()
- old = self.blockSignals(True)
- self.setChecked(modeDict["mode"] == "pan")
- self.blockSignals(old)
-
- def _actionTriggered(self, checked=False):
- plot = self.plot
- if plot is not None:
- plot.setInteractiveMode('pan', source=self)