summaryrefslogtreecommitdiff
path: root/silx/gui/plot/utils/axis.py
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@debian.org>2022-02-02 14:19:58 +0100
committerPicca Frédéric-Emmanuel <picca@debian.org>2022-02-02 14:19:58 +0100
commit4e774db12d5ebe7a20eded6dd434a289e27999e5 (patch)
treea9822974ba45196f1e3740995ab157d6eb214a04 /silx/gui/plot/utils/axis.py
parentd3194b1a9c4404ba93afac43d97172ab24c57098 (diff)
New upstream version 1.0.0+dfsg
Diffstat (limited to 'silx/gui/plot/utils/axis.py')
-rw-r--r--silx/gui/plot/utils/axis.py403
1 files changed, 0 insertions, 403 deletions
diff --git a/silx/gui/plot/utils/axis.py b/silx/gui/plot/utils/axis.py
deleted file mode 100644
index 693e8eb..0000000
--- a/silx/gui/plot/utils/axis.py
+++ /dev/null
@@ -1,403 +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 module contains utils class for axes management.
-"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "20/11/2018"
-
-import functools
-import logging
-from contextlib import contextmanager
-import weakref
-import silx.utils.weakref as silxWeakref
-from silx.gui.plot.items.axis import Axis, XAxis, YAxis
-
-try:
- from ...qt.inspect import isValid as _isQObjectValid
-except ImportError: # PySide(1) fallback
- def _isQObjectValid(obj):
- return True
-
-
-_logger = logging.getLogger(__name__)
-
-
-class SyncAxes(object):
- """Synchronize a set of plot axes together.
-
- It is created with the expected axes and starts to synchronize them.
-
- It can be customized to synchronize limits, scale, and direction of axes
- together. By default everything is synchronized.
-
- The API :meth:`start` and :meth:`stop` can be used to enable/disable the
- synchronization while this object is still alive.
-
- If this object is destroyed the synchronization stop.
-
- .. versionadded:: 0.6
- """
-
- def __init__(self, axes,
- syncLimits=True,
- syncScale=True,
- syncDirection=True,
- syncCenter=False,
- syncZoom=False,
- filterHiddenPlots=False
- ):
- """
- Constructor
-
- :param list(Axis) axes: A list of axes to synchronize together
- :param bool syncLimits: Synchronize axes limits
- :param bool syncScale: Synchronize axes scale
- :param bool syncDirection: Synchronize axes direction
- :param bool syncCenter: Synchronize the center of the axes in the center
- of the plots
- :param bool syncZoom: Synchronize the zoom of the plot
- :param bool filterHiddenPlots: True to avoid updating hidden plots.
- Default: False.
- """
- object.__init__(self)
-
- def implies(x, y): return bool(y ** x)
-
- assert(implies(syncZoom, not syncLimits))
- assert(implies(syncCenter, not syncLimits))
- assert(implies(syncLimits, not syncCenter))
- assert(implies(syncLimits, not syncZoom))
-
- self.__filterHiddenPlots = filterHiddenPlots
- self.__locked = False
- self.__axisRefs = []
- self.__syncLimits = syncLimits
- self.__syncScale = syncScale
- self.__syncDirection = syncDirection
- self.__syncCenter = syncCenter
- self.__syncZoom = syncZoom
- self.__callbacks = None
- self.__lastMainAxis = None
-
- for axis in axes:
- self.addAxis(axis)
-
- self.start()
-
- def start(self):
- """Start synchronizing axes together.
-
- The first axis is used as the reference for the first synchronization.
- After that, any changes to any axes will be used to synchronize other
- axes.
- """
- if self.isSynchronizing():
- raise RuntimeError("Axes already synchronized")
- self.__callbacks = {}
-
- axes = self.__getAxes()
-
- # register callback for further sync
- for axis in axes:
- self.__connectAxes(axis)
- self.synchronize()
-
- def isSynchronizing(self):
- """Returns true if events are connected to the axes to synchronize them
- all together
-
- :rtype: bool
- """
- return self.__callbacks is not None
-
- def __connectAxes(self, axis):
- refAxis = weakref.ref(axis)
- callbacks = []
- if self.__syncLimits:
- # the weakref is needed to be able ignore self references
- callback = silxWeakref.WeakMethodProxy(self.__axisLimitsChanged)
- callback = functools.partial(callback, refAxis)
- sig = axis.sigLimitsChanged
- sig.connect(callback)
- callbacks.append(("sigLimitsChanged", callback))
- elif self.__syncCenter and self.__syncZoom:
- # the weakref is needed to be able ignore self references
- callback = silxWeakref.WeakMethodProxy(self.__axisCenterAndZoomChanged)
- callback = functools.partial(callback, refAxis)
- sig = axis.sigLimitsChanged
- sig.connect(callback)
- callbacks.append(("sigLimitsChanged", callback))
- elif self.__syncZoom:
- raise NotImplementedError()
- elif self.__syncCenter:
- # the weakref is needed to be able ignore self references
- callback = silxWeakref.WeakMethodProxy(self.__axisCenterChanged)
- callback = functools.partial(callback, refAxis)
- sig = axis.sigLimitsChanged
- sig.connect(callback)
- callbacks.append(("sigLimitsChanged", callback))
- if self.__syncScale:
- # the weakref is needed to be able ignore self references
- callback = silxWeakref.WeakMethodProxy(self.__axisScaleChanged)
- callback = functools.partial(callback, refAxis)
- sig = axis.sigScaleChanged
- sig.connect(callback)
- callbacks.append(("sigScaleChanged", callback))
- if self.__syncDirection:
- # the weakref is needed to be able ignore self references
- callback = silxWeakref.WeakMethodProxy(self.__axisInvertedChanged)
- callback = functools.partial(callback, refAxis)
- sig = axis.sigInvertedChanged
- sig.connect(callback)
- callbacks.append(("sigInvertedChanged", callback))
-
- if self.__filterHiddenPlots:
- # the weakref is needed to be able ignore self references
- callback = silxWeakref.WeakMethodProxy(self.__axisVisibilityChanged)
- callback = functools.partial(callback, refAxis)
- plot = axis._getPlot()
- plot.sigVisibilityChanged.connect(callback)
- callbacks.append(("sigVisibilityChanged", callback))
-
- self.__callbacks[refAxis] = callbacks
-
- def __disconnectAxes(self, axis):
- if axis is not None and _isQObjectValid(axis):
- ref = weakref.ref(axis)
- callbacks = self.__callbacks.pop(ref)
- for sigName, callback in callbacks:
- if sigName == "sigVisibilityChanged":
- obj = axis._getPlot()
- else:
- obj = axis
- if obj is not None:
- sig = getattr(obj, sigName)
- sig.disconnect(callback)
-
- def addAxis(self, axis):
- """Add a new axes to synchronize.
-
- :param ~silx.gui.plot.items.Axis axis: The axis to synchronize
- """
- self.__axisRefs.append(weakref.ref(axis))
- if self.isSynchronizing():
- self.__connectAxes(axis)
- # This could be done faster as only this axis have to be fixed
- self.synchronize()
-
- def removeAxis(self, axis):
- """Remove an axis from the synchronized axes.
-
- :param ~silx.gui.plot.items.Axis axis: The axis to remove
- """
- ref = weakref.ref(axis)
- self.__axisRefs.remove(ref)
- if self.isSynchronizing():
- self.__disconnectAxes(axis)
-
- def synchronize(self, mainAxis=None):
- """Synchronize programatically all the axes.
-
- :param ~silx.gui.plot.items.Axis mainAxis:
- The axis to take as reference (Default: the first axis).
- """
- # sync the current state
- axes = self.__getAxes()
- if len(axes) == 0:
- return
-
- if mainAxis is None:
- mainAxis = axes[0]
-
- refMainAxis = weakref.ref(mainAxis)
- if self.__syncLimits:
- self.__axisLimitsChanged(refMainAxis, *mainAxis.getLimits())
- elif self.__syncCenter and self.__syncZoom:
- self.__axisCenterAndZoomChanged(refMainAxis, *mainAxis.getLimits())
- elif self.__syncCenter:
- self.__axisCenterChanged(refMainAxis, *mainAxis.getLimits())
- if self.__syncScale:
- self.__axisScaleChanged(refMainAxis, mainAxis.getScale())
- if self.__syncDirection:
- self.__axisInvertedChanged(refMainAxis, mainAxis.isInverted())
-
- def stop(self):
- """Stop the synchronization of the axes"""
- if not self.isSynchronizing():
- raise RuntimeError("Axes not synchronized")
- for ref in list(self.__callbacks.keys()):
- axis = ref()
- self.__disconnectAxes(axis)
- self.__callbacks = None
-
- def __del__(self):
- """Destructor"""
- # clean up references
- if self.__callbacks is not None:
- self.stop()
-
- def __getAxes(self):
- """Returns list of existing axes.
-
- :rtype: List[Axis]
- """
- axes = [ref() for ref in self.__axisRefs]
- return [axis for axis in axes if axis is not None]
-
- @contextmanager
- def __inhibitSignals(self):
- self.__locked = True
- yield
- self.__locked = False
-
- def __axesToUpdate(self, changedAxis):
- for axis in self.__getAxes():
- if axis is changedAxis:
- continue
- if self.__filterHiddenPlots:
- plot = axis._getPlot()
- if not plot.isVisible():
- continue
- yield axis
-
- def __axisVisibilityChanged(self, changedAxis, isVisible):
- if not isVisible:
- return
- if self.__locked:
- return
- changedAxis = changedAxis()
- if self.__lastMainAxis is None:
- self.__lastMainAxis = self.__axisRefs[0]
- mainAxis = self.__lastMainAxis
- mainAxis = mainAxis()
- self.synchronize(mainAxis=mainAxis)
- # force back the main axis
- self.__lastMainAxis = weakref.ref(mainAxis)
-
- def __getAxesCenter(self, axis, vmin, vmax):
- """Returns the value displayed in the center of this axis range.
-
- :rtype: float
- """
- scale = axis.getScale()
- if scale == Axis.LINEAR:
- center = (vmin + vmax) * 0.5
- else:
- raise NotImplementedError("Log scale not implemented")
- return center
-
- def __getRangeInPixel(self, axis):
- """Returns the size of the axis in pixel"""
- bounds = axis._getPlot().getPlotBoundsInPixels()
- # bounds: left, top, width, height
- if isinstance(axis, XAxis):
- return bounds[2]
- elif isinstance(axis, YAxis):
- return bounds[3]
- else:
- assert(False)
-
- def __getLimitsFromCenter(self, axis, pos, pixelSize=None):
- """Returns the limits to apply to this axis to move the `pos` into the
- center of this axis.
-
- :param Axis axis:
- :param float pos: Position in the center of the computed limits
- :param Union[None,float] pixelSize: Pixel size to apply to compute the
- limits. If `None` the current pixel size is applyed.
- """
- scale = axis.getScale()
- if scale == Axis.LINEAR:
- if pixelSize is None:
- # Use the current pixel size of the axis
- limits = axis.getLimits()
- valueRange = limits[0] - limits[1]
- a = pos - valueRange * 0.5
- b = pos + valueRange * 0.5
- else:
- pixelRange = self.__getRangeInPixel(axis)
- a = pos - pixelRange * 0.5 * pixelSize
- b = pos + pixelRange * 0.5 * pixelSize
-
- else:
- raise NotImplementedError("Log scale not implemented")
- if a > b:
- return b, a
- return a, b
-
- def __axisLimitsChanged(self, changedAxis, vmin, vmax):
- if self.__locked:
- return
- self.__lastMainAxis = changedAxis
- changedAxis = changedAxis()
- with self.__inhibitSignals():
- for axis in self.__axesToUpdate(changedAxis):
- axis.setLimits(vmin, vmax)
-
- def __axisCenterAndZoomChanged(self, changedAxis, vmin, vmax):
- if self.__locked:
- return
- self.__lastMainAxis = changedAxis
- changedAxis = changedAxis()
- with self.__inhibitSignals():
- center = self.__getAxesCenter(changedAxis, vmin, vmax)
- pixelRange = self.__getRangeInPixel(changedAxis)
- if pixelRange == 0:
- return
- pixelSize = (vmax - vmin) / pixelRange
- for axis in self.__axesToUpdate(changedAxis):
- vmin, vmax = self.__getLimitsFromCenter(axis, center, pixelSize)
- axis.setLimits(vmin, vmax)
-
- def __axisCenterChanged(self, changedAxis, vmin, vmax):
- if self.__locked:
- return
- self.__lastMainAxis = changedAxis
- changedAxis = changedAxis()
- with self.__inhibitSignals():
- center = self.__getAxesCenter(changedAxis, vmin, vmax)
- for axis in self.__axesToUpdate(changedAxis):
- vmin, vmax = self.__getLimitsFromCenter(axis, center)
- axis.setLimits(vmin, vmax)
-
- def __axisScaleChanged(self, changedAxis, scale):
- if self.__locked:
- return
- self.__lastMainAxis = changedAxis
- changedAxis = changedAxis()
- with self.__inhibitSignals():
- for axis in self.__axesToUpdate(changedAxis):
- axis.setScale(scale)
-
- def __axisInvertedChanged(self, changedAxis, isInverted):
- if self.__locked:
- return
- self.__lastMainAxis = changedAxis
- changedAxis = changedAxis()
- with self.__inhibitSignals():
- for axis in self.__axesToUpdate(changedAxis):
- axis.setInverted(isInverted)