summaryrefslogtreecommitdiff
path: root/silx/gui/plot/_utils/panzoom.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/_utils/panzoom.py')
-rw-r--r--silx/gui/plot/_utils/panzoom.py154
1 files changed, 145 insertions, 9 deletions
diff --git a/silx/gui/plot/_utils/panzoom.py b/silx/gui/plot/_utils/panzoom.py
index bec31df..3946a04 100644
--- a/silx/gui/plot/_utils/panzoom.py
+++ b/silx/gui/plot/_utils/panzoom.py
@@ -24,13 +24,12 @@
# ###########################################################################*/
"""Functions to apply pan and zoom on a Plot"""
-__authors__ = ["T. Vincent"]
+__authors__ = ["T. Vincent", "V. Valls"]
__license__ = "MIT"
-__date__ = "21/03/2017"
+__date__ = "08/08/2017"
import math
-
import numpy
@@ -93,8 +92,8 @@ def applyZoomToPlot(plot, scaleF, center=None):
:param center: (x, y) coords in pixel coordinates of the zoom center.
:type center: 2-tuple of float
"""
- xMin, xMax = plot.getGraphXLimits()
- yMin, yMax = plot.getGraphYLimits()
+ xMin, xMax = plot.getXAxis().getLimits()
+ yMin, yMax = plot.getYAxis().getLimits()
if center is None:
left, top, width, height = plot.getPlotBoundsInPixels()
@@ -106,17 +105,17 @@ def applyZoomToPlot(plot, scaleF, center=None):
assert dataCenterPos is not None
xMin, xMax = scale1DRange(xMin, xMax, dataCenterPos[0], scaleF,
- plot.isXAxisLogarithmic())
+ plot.getXAxis()._isLogarithmic())
yMin, yMax = scale1DRange(yMin, yMax, dataCenterPos[1], scaleF,
- plot.isYAxisLogarithmic())
+ plot.getYAxis()._isLogarithmic())
dataPos = plot.pixelToData(cx, cy, axis="right")
assert dataPos is not None
y2Center = dataPos[1]
- y2Min, y2Max = plot.getGraphYLimits(axis="right")
+ y2Min, y2Max = plot.getYAxis(axis="right").getLimits()
y2Min, y2Max = scale1DRange(y2Min, y2Max, y2Center, scaleF,
- plot.isYAxisLogarithmic())
+ plot.getYAxis()._isLogarithmic())
plot.setLimits(xMin, xMax, yMin, yMax, y2Min, y2Max)
@@ -154,3 +153,140 @@ def applyPan(min_, max_, panFactor, isLog10):
if newMin > - float('inf') and newMax < float('inf'):
min_, max_ = newMin, newMax
return min_, max_
+
+
+class _Unset(object):
+ """To be able to have distinction between None and unset"""
+ pass
+
+
+class ViewConstraints(object):
+ """
+ Store constraints applied on the view box and compute the resulting view box.
+ """
+
+ def __init__(self):
+ self._min = [None, None]
+ self._max = [None, None]
+ self._minRange = [None, None]
+ self._maxRange = [None, None]
+
+ def update(self, xMin=_Unset, xMax=_Unset,
+ yMin=_Unset, yMax=_Unset,
+ minXRange=_Unset, maxXRange=_Unset,
+ minYRange=_Unset, maxYRange=_Unset):
+ """
+ Update the constraints managed by the object
+
+ The constraints are the same as the ones provided by PyQtGraph.
+
+ :param float xMin: Minimum allowed x-axis value.
+ (default do not change the stat, None remove the constraint)
+ :param float xMax: Maximum allowed x-axis value.
+ (default do not change the stat, None remove the constraint)
+ :param float yMin: Minimum allowed y-axis value.
+ (default do not change the stat, None remove the constraint)
+ :param float yMax: Maximum allowed y-axis value.
+ (default do not change the stat, None remove the constraint)
+ :param float minXRange: Minimum allowed left-to-right span across the
+ view (default do not change the stat, None remove the constraint)
+ :param float maxXRange: Maximum allowed left-to-right span across the
+ view (default do not change the stat, None remove the constraint)
+ :param float minYRange: Minimum allowed top-to-bottom span across the
+ view (default do not change the stat, None remove the constraint)
+ :param float maxYRange: Maximum allowed top-to-bottom span across the
+ view (default do not change the stat, None remove the constraint)
+ :return: True if the constraints was changed
+ """
+ updated = False
+
+ minRange = [minXRange, minYRange]
+ maxRange = [maxXRange, maxYRange]
+ minPos = [xMin, yMin]
+ maxPos = [xMax, yMax]
+
+ for axis in range(2):
+
+ value = minPos[axis]
+ if value is not _Unset and value != self._min[axis]:
+ self._min[axis] = value
+ updated = True
+
+ value = maxPos[axis]
+ if value is not _Unset and value != self._max[axis]:
+ self._max[axis] = value
+ updated = True
+
+ value = minRange[axis]
+ if value is not _Unset and value != self._minRange[axis]:
+ self._minRange[axis] = value
+ updated = True
+
+ value = maxRange[axis]
+ if value is not _Unset and value != self._maxRange[axis]:
+ self._maxRange[axis] = value
+ updated = True
+
+ # Sanity checks
+
+ for axis in range(2):
+ if self._maxRange[axis] is not None and self._min[axis] is not None and self._max[axis] is not None:
+ # max range cannot be larger than bounds
+ diff = self._max[axis] - self._min[axis]
+ self._maxRange[axis] = min(self._maxRange[axis], diff)
+ updated = True
+
+ return updated
+
+ def normalize(self, xMin, xMax, yMin, yMax, allow_scaling=True):
+ """Normalize a view range defined by x and y corners using predefined
+ containts.
+
+ :param float xMin: Min position of the x-axis
+ :param float xMax: Max position of the x-axis
+ :param float yMin: Min position of the y-axis
+ :param float yMax: Max position of the y-axis
+ :param bool allow_scaling: Allow or not to apply scaling for the
+ normalization. Used according to the interaction mode.
+ :return: A normalized tuple of (xMin, xMax, yMin, yMax)
+ """
+ viewRange = [[xMin, xMax], [yMin, yMax]]
+
+ for axis in range(2):
+ # clamp xRange and yRange
+ if allow_scaling:
+ diff = viewRange[axis][1] - viewRange[axis][0]
+ delta = None
+ if self._maxRange[axis] is not None and diff > self._maxRange[axis]:
+ delta = self._maxRange[axis] - diff
+ elif self._minRange[axis] is not None and diff < self._minRange[axis]:
+ delta = self._minRange[axis] - diff
+ if delta is not None:
+ viewRange[axis][0] -= delta * 0.5
+ viewRange[axis][1] += delta * 0.5
+
+ # clamp min and max positions
+ outMin = self._min[axis] is not None and viewRange[axis][0] < self._min[axis]
+ outMax = self._max[axis] is not None and viewRange[axis][1] > self._max[axis]
+
+ if outMin and outMax:
+ if allow_scaling:
+ # we can clamp both sides
+ viewRange[axis][0] = self._min[axis]
+ viewRange[axis][1] = self._max[axis]
+ else:
+ # center the result
+ delta = viewRange[axis][1] - viewRange[axis][0]
+ mid = self._min[axis] + self._max[axis] - self._min[axis]
+ viewRange[axis][0] = mid - delta
+ viewRange[axis][1] = mid + delta
+ elif outMin:
+ delta = self._min[axis] - viewRange[axis][0]
+ viewRange[axis][0] += delta
+ viewRange[axis][1] += delta
+ elif outMax:
+ delta = self._max[axis] - viewRange[axis][1]
+ viewRange[axis][0] += delta
+ viewRange[axis][1] += delta
+
+ return viewRange[0][0], viewRange[0][1], viewRange[1][0], viewRange[1][1]