diff options
author | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2019-05-28 08:16:16 +0200 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2019-05-28 08:16:16 +0200 |
commit | a763e5d1b3921b3194f3d4e94ab9de3fbe08bbdd (patch) | |
tree | 45d462ed36a5522e9f3b9fde6c4ec4918c2ae8e3 /silx/gui/plot/PlotInteraction.py | |
parent | cebdc9244c019224846cb8d2668080fe386a6adc (diff) |
New upstream version 0.10.1+dfsg
Diffstat (limited to 'silx/gui/plot/PlotInteraction.py')
-rw-r--r-- | silx/gui/plot/PlotInteraction.py | 148 |
1 files changed, 124 insertions, 24 deletions
diff --git a/silx/gui/plot/PlotInteraction.py b/silx/gui/plot/PlotInteraction.py index 356bda6..27abd10 100644 --- a/silx/gui/plot/PlotInteraction.py +++ b/silx/gui/plot/PlotInteraction.py @@ -26,7 +26,7 @@ __authors__ = ["T. Vincent"] __license__ = "MIT" -__date__ = "24/04/2018" +__date__ = "15/02/2019" import math @@ -96,10 +96,18 @@ class _PlotInteraction(object): fill = fill != 'none' # TODO not very nice either + greyed = colors.greyed(color)[0] + if greyed < 0.5: + color2 = "white" + else: + color2 = "black" + self.plot.addItem(points[:, 0], points[:, 1], legend=legend, replace=False, - shape=shape, color=color, fill=fill, + shape=shape, fill=fill, + color=color, linebgcolor=color2, linestyle="--", overlay=True) + self._selectionAreas.add(legend) def resetSelectionArea(self): @@ -274,6 +282,8 @@ class Zoom(_ZoomOnWheel): and zoom on mouse wheel. """ + SURFACE_THRESHOLD = 5 + def __init__(self, plot, color): self.color = color @@ -347,35 +357,44 @@ class Zoom(_ZoomOnWheel): self.setSelectionArea(corners, fill='none', color=self.color) - def endDrag(self, startPos, endPos): - x0, y0 = startPos - x1, y1 = endPos + def _zoom(self, x0, y0, x1, y1): + """Zoom to the rectangle view x0,y0 x1,y1. + """ + startPos = x0, y0 + endPos = x1, y1 + + # Store current zoom state in stack + self.plot.getLimitsHistory().push() - if x0 != x1 or y0 != y1: # Avoid empty zoom area - # Store current zoom state in stack - self.plot.getLimitsHistory().push() + if self.plot.isKeepDataAspectRatio(): + x0, y0, x1, y1 = self._areaWithAspectRatio(x0, y0, x1, y1) + + # Convert to data space and set limits + x0, y0 = self.plot.pixelToData(x0, y0, check=False) - if self.plot.isKeepDataAspectRatio(): - x0, y0, x1, y1 = self._areaWithAspectRatio(x0, y0, x1, y1) + dataPos = self.plot.pixelToData( + startPos[0], startPos[1], axis="right", check=False) + y2_0 = dataPos[1] - # Convert to data space and set limits - x0, y0 = self.plot.pixelToData(x0, y0, check=False) + x1, y1 = self.plot.pixelToData(x1, y1, check=False) - dataPos = self.plot.pixelToData( - startPos[0], startPos[1], axis="right", check=False) - y2_0 = dataPos[1] + dataPos = self.plot.pixelToData( + endPos[0], endPos[1], axis="right", check=False) + y2_1 = dataPos[1] - x1, y1 = self.plot.pixelToData(x1, y1, check=False) + xMin, xMax = min(x0, x1), max(x0, x1) + yMin, yMax = min(y0, y1), max(y0, y1) + y2Min, y2Max = min(y2_0, y2_1), max(y2_0, y2_1) - dataPos = self.plot.pixelToData( - endPos[0], endPos[1], axis="right", check=False) - y2_1 = dataPos[1] + self.plot.setLimits(xMin, xMax, yMin, yMax, y2Min, y2Max) - xMin, xMax = min(x0, x1), max(x0, x1) - yMin, yMax = min(y0, y1), max(y0, y1) - y2Min, y2Max = min(y2_0, y2_1), max(y2_0, y2_1) + def endDrag(self, startPos, endPos): + x0, y0 = startPos + x1, y1 = endPos - self.plot.setLimits(xMin, xMax, yMin, yMax, y2Min, y2Max) + if abs(x0 - x1) * abs(y0 - y1) >= self.SURFACE_THRESHOLD: + # Avoid empty zoom area + self._zoom(x0, y0, x1, y1) self.resetSelectionArea() @@ -544,7 +563,6 @@ class SelectPolygon(Select): return self.DRAG_THRESHOLD_DIST * ratio - class Select2Points(Select): """Base class for drawing selection based on 2 input points.""" class Idle(State): @@ -603,6 +621,87 @@ class Select2Points(Select): self.cancelSelect() +class SelectEllipse(Select2Points): + """Drawing ellipse selection area state machine.""" + def beginSelect(self, x, y): + self.center = self.plot.pixelToData(x, y) + assert self.center is not None + + def _getEllipseSize(self, pointInEllipse): + """ + Returns the size from the center to the bounding box of the ellipse. + + :param Tuple[float,float] pointInEllipse: A point of the ellipse + :rtype: Tuple[float,float] + """ + x = abs(self.center[0] - pointInEllipse[0]) + y = abs(self.center[1] - pointInEllipse[1]) + if x == 0 or y == 0: + return x, y + # Ellipse definitions + # e: eccentricity + # a: length fron center to bounding box width + # b: length fron center to bounding box height + # Equations + # (1) b < a + # (2) For x,y a point in the ellipse: x^2/a^2 + y^2/b^2 = 1 + # (3) b = a * sqrt(1-e^2) + # (4) e = sqrt(a^2 - b^2) / a + + # The eccentricity of the ellipse defined by a,b=x,y is the same + # as the one we are searching for. + swap = x < y + if swap: + x, y = y, x + e = math.sqrt(x**2 - y**2) / x + # From (2) using (3) to replace b + # a^2 = x^2 + y^2 / (1-e^2) + a = math.sqrt(x**2 + y**2 / (1.0 - e**2)) + b = a * math.sqrt(1 - e**2) + if swap: + a, b = b, a + return a, b + + def select(self, x, y): + dataPos = self.plot.pixelToData(x, y) + assert dataPos is not None + width, height = self._getEllipseSize(dataPos) + + # Circle used for circle preview + nbpoints = 27. + angles = numpy.arange(nbpoints) * numpy.pi * 2.0 / nbpoints + circleShape = numpy.array((numpy.cos(angles) * width, + numpy.sin(angles) * height)).T + circleShape += numpy.array(self.center) + + self.setSelectionArea(circleShape, + shape="polygon", + fill='hatch', + color=self.color) + + eventDict = prepareDrawingSignal('drawingProgress', + 'ellipse', + (self.center, (width, height)), + self.parameters) + self.plot.notify(**eventDict) + + def endSelect(self, x, y): + self.resetSelectionArea() + + dataPos = self.plot.pixelToData(x, y) + assert dataPos is not None + width, height = self._getEllipseSize(dataPos) + + eventDict = prepareDrawingSignal('drawingFinished', + 'ellipse', + (self.center, (width, height)), + self.parameters) + self.plot.notify(**eventDict) + + def cancelSelect(self): + self.resetSelectionArea() + + class SelectRectangle(Select2Points): """Drawing rectangle selection area state machine.""" def beginSelect(self, x, y): @@ -1488,6 +1587,7 @@ class PlotInteraction(object): _DRAW_MODES = { 'polygon': SelectPolygon, 'rectangle': SelectRectangle, + 'ellipse': SelectEllipse, 'line': SelectLine, 'vline': SelectVLine, 'hline': SelectHLine, |