summaryrefslogtreecommitdiff
path: root/silx/gui/plot/PlotInteraction.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/PlotInteraction.py')
-rw-r--r--silx/gui/plot/PlotInteraction.py148
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,