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.py292
1 files changed, 0 insertions, 292 deletions
diff --git a/silx/gui/plot/_utils/panzoom.py b/silx/gui/plot/_utils/panzoom.py
deleted file mode 100644
index 3946a04..0000000
--- a/silx/gui/plot/_utils/panzoom.py
+++ /dev/null
@@ -1,292 +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.
-#
-# ###########################################################################*/
-"""Functions to apply pan and zoom on a Plot"""
-
-__authors__ = ["T. Vincent", "V. Valls"]
-__license__ = "MIT"
-__date__ = "08/08/2017"
-
-
-import math
-import numpy
-
-
-# Float 32 info ###############################################################
-# Using min/max value below limits of float32
-# so operation with such value (e.g., max - min) do not overflow
-
-FLOAT32_SAFE_MIN = -1e37
-FLOAT32_MINPOS = numpy.finfo(numpy.float32).tiny
-FLOAT32_SAFE_MAX = 1e37
-# TODO double support
-
-
-def scale1DRange(min_, max_, center, scale, isLog):
- """Scale a 1D range given a scale factor and an center point.
-
- Keeps the values in a smaller range than float32.
-
- :param float min_: The current min value of the range.
- :param float max_: The current max value of the range.
- :param float center: The center of the zoom (i.e., invariant point).
- :param float scale: The scale to use for zoom
- :param bool isLog: Whether using log scale or not.
- :return: The zoomed range.
- :rtype: tuple of 2 floats: (min, max)
- """
- if isLog:
- # Min and center can be < 0 when
- # autoscale is off and switch to log scale
- # max_ < 0 should not happen
- min_ = numpy.log10(min_) if min_ > 0. else FLOAT32_MINPOS
- center = numpy.log10(center) if center > 0. else FLOAT32_MINPOS
- max_ = numpy.log10(max_) if max_ > 0. else FLOAT32_MINPOS
-
- if min_ == max_:
- return min_, max_
-
- offset = (center - min_) / (max_ - min_)
- range_ = (max_ - min_) / scale
- newMin = center - offset * range_
- newMax = center + (1. - offset) * range_
-
- if isLog:
- # No overflow as exponent is log10 of a float32
- newMin = pow(10., newMin)
- newMax = pow(10., newMax)
- newMin = numpy.clip(newMin, FLOAT32_MINPOS, FLOAT32_SAFE_MAX)
- newMax = numpy.clip(newMax, FLOAT32_MINPOS, FLOAT32_SAFE_MAX)
- else:
- newMin = numpy.clip(newMin, FLOAT32_SAFE_MIN, FLOAT32_SAFE_MAX)
- newMax = numpy.clip(newMax, FLOAT32_SAFE_MIN, FLOAT32_SAFE_MAX)
- return newMin, newMax
-
-
-def applyZoomToPlot(plot, scaleF, center=None):
- """Zoom in/out plot given a scale and a center point.
-
- :param plot: The plot on which to apply zoom.
- :param float scaleF: Scale factor of zoom.
- :param center: (x, y) coords in pixel coordinates of the zoom center.
- :type center: 2-tuple of float
- """
- xMin, xMax = plot.getXAxis().getLimits()
- yMin, yMax = plot.getYAxis().getLimits()
-
- if center is None:
- left, top, width, height = plot.getPlotBoundsInPixels()
- cx, cy = left + width // 2, top + height // 2
- else:
- cx, cy = center
-
- dataCenterPos = plot.pixelToData(cx, cy)
- assert dataCenterPos is not None
-
- xMin, xMax = scale1DRange(xMin, xMax, dataCenterPos[0], scaleF,
- plot.getXAxis()._isLogarithmic())
-
- yMin, yMax = scale1DRange(yMin, yMax, dataCenterPos[1], scaleF,
- plot.getYAxis()._isLogarithmic())
-
- dataPos = plot.pixelToData(cx, cy, axis="right")
- assert dataPos is not None
- y2Center = dataPos[1]
- y2Min, y2Max = plot.getYAxis(axis="right").getLimits()
- y2Min, y2Max = scale1DRange(y2Min, y2Max, y2Center, scaleF,
- plot.getYAxis()._isLogarithmic())
-
- plot.setLimits(xMin, xMax, yMin, yMax, y2Min, y2Max)
-
-
-def applyPan(min_, max_, panFactor, isLog10):
- """Returns a new range with applied panning.
-
- Moves the range according to panFactor.
- If isLog10 is True, converts to log10 before moving.
-
- :param float min_: Min value of the data range to pan.
- :param float max_: Max value of the data range to pan.
- Must be >= min.
- :param float panFactor: Signed proportion of the range to use for pan.
- :param bool isLog10: True if log10 scale, False if linear scale.
- :return: New min and max value with pan applied.
- :rtype: 2-tuple of float.
- """
- if isLog10 and min_ > 0.:
- # Negative range and log scale can happen with matplotlib
- logMin, logMax = math.log10(min_), math.log10(max_)
- logOffset = panFactor * (logMax - logMin)
- newMin = pow(10., logMin + logOffset)
- newMax = pow(10., logMax + logOffset)
-
- # Takes care of out-of-range values
- if newMin > 0. and newMax < float('inf'):
- min_, max_ = newMin, newMax
-
- else:
- offset = panFactor * (max_ - min_)
- newMin, newMax = min_ + offset, max_ + offset
-
- # Takes care of out-of-range values
- 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]