import numpy as np
from qtpy.QtGui import *
from qtpy.QtCore import *
[docs]class Draggable(object):
"""
The Draggable class implements low-level clicking and dragging on widgets.
"""
def __init__(self, x_obj, y_obj, background, cutoff=5, lock_dim=None, line_color=QColor('#FFFFFF')):
"""
Construct a draggable object
x_obj: Numpy array or Python float containing the x coordinate(s) in pixels of the draggable object.
y_obj: Numpy array or Python float containing the y coordinate(s) in pixels of the draggable object.
background: A QPixmap containing the image displayed in the widget.
cutoff: The radius of the buffer around each vertex when looking for clicks. Default is 5 px.
lock_dim: A dimension to lock in place when doing the dragging. Allowed values are 'x' (dragging
is allowed in the y dimension only), 'y' (dragging is allowed in the x direction
only). Default is to allow dragging in both dimensions.
line_color: The color to use for the line drawn while dragging.
"""
self._background = background
self._x_obj = x_obj
self._y_obj = y_obj
self._cutoff = cutoff
self._lock_dim = lock_dim
self._line_color = line_color
self._drag_idx = None
self._save_bitmap = None
self._drag_buffer = 5
self._click_start = None
if type(self._x_obj) != np.ma.MaskedArray:
self._x_obj = np.ma.array([ self._x_obj ])
if type(self._y_obj) != np.ma.MaskedArray:
self._y_obj = np.ma.array([ self._y_obj ])
[docs] def click(self, click_x, click_y):
"""
Check to see whether the user clicked on a vertex in this profile. If they did, initialize
the drag operation.
click_x: x coordinate in pixels of the click
click_y: y coordinate in pixels of the click
Returns a boolean specifying whether a drag operation was initialized.
"""
dists = np.hypot(click_x - self._x_obj, click_y - self._y_obj)
if np.any(dists <= self._cutoff):
self._drag_idx = np.argmin(dists)
self._click_start = (click_x, click_y)
return self._drag_idx is not None
[docs] def drag(self, drag_x, drag_y, restrictions=None):
"""
Drag a point or a vertex in a line. Applies restrictions to the drag point based on the lock_dim
argument to the constructor and the restrictions argument to this method.
drag_x: x coordinate in pixels of the point to drag to.
drag_y: y coordinate in pixels of the point to drag to.
restrictions: A callable object (e.g. callback function) that takes two arguments (x and y
coordinates) and returns a modified x and y based on restrictions on the
dragging. Used, for example, to restrict dragging on the temperature line
to be greater than the dewpoint.
"""
if self._drag_idx is None:
return
if self._lock_dim == 'x':
drag_x = self._x_obj[self._drag_idx]
elif self._lock_dim == 'y':
drag_y = self._y_obj[self._drag_idx]
if restrictions is not None:
drag_x, drag_y = restrictions(drag_x, drag_y)
if len(self._x_obj) == 1:
self._dragPoint(drag_x, drag_y)
else:
self._dragLine(drag_x, drag_y)
def _dragLine(self, drag_x, drag_y):
"""
Drag a line [private method].
drag_x: x coordinate in pixels of the drag point.
drag_y: y coordinate in pixels of the drag point.
"""
lb_idx, ub_idx = max(self._drag_idx - 1, 0), min(self._drag_idx + 1, self._x_obj.shape[0] - 1)
while lb_idx >= 0 and (self._x_obj.mask[lb_idx] or self._y_obj.mask[lb_idx]):
lb_idx -= 1
while ub_idx < self._x_obj.shape[0] and (self._x_obj.mask[ub_idx] or self._y_obj.mask[ub_idx]):
ub_idx += 1
if lb_idx != -1:
lb_x, ub_x = min(drag_x, self._x_obj[lb_idx]), max(drag_x, self._x_obj[lb_idx])
lb_y, ub_y = min(drag_y, self._y_obj[lb_idx]), max(drag_y, self._y_obj[lb_idx])
else:
lb_x = ub_x = drag_x
lb_y = ub_y = drag_y
if ub_idx != self._x_obj.shape[0]:
lb_x, ub_x = min(lb_x, self._x_obj[ub_idx]), max(ub_x, self._x_obj[ub_idx])
lb_y, ub_y = min(lb_y, self._y_obj[ub_idx]), max(ub_y, self._y_obj[ub_idx])
qp = QPainter()
qp.begin(self._background)
self._restoreBitmap(qp)
self._saveBitmap(lb_x, lb_y, ub_x, ub_y)
pen = QPen(self._line_color, 1, Qt.SolidLine)
qp.setPen(pen)
if lb_idx != -1:
x1, y1 = self._x_obj[lb_idx], self._y_obj[lb_idx]
x2, y2 = drag_x, drag_y
qp.drawLine(x1, y1, x2, y2)
if ub_idx != self._x_obj.shape[0]:
x1, y1 = drag_x, drag_y
x2, y2 = self._x_obj[ub_idx], self._y_obj[ub_idx]
qp.drawLine(x1, y1, x2, y2)
qp.end()
def _dragPoint(self, drag_x, drag_y):
"""
Drag a point [private method].
drag_x: x coordinate in pixels of the drag point
drag_y: y coordinate in pixels of the drag point
"""
qp = QPainter()
qp.begin(self._background)
self._restoreBitmap(qp)
self._saveBitmap(drag_x, drag_y, drag_x, drag_y)
pen = QPen(self._line_color, 1, Qt.SolidLine)
qp.setPen(pen)
qp.drawEllipse(QPointF(drag_x, drag_y), 3, 3)
qp.end()
[docs] def release(self, rls_x, rls_y, restrictions=None):
"""
Release the drag, which finishes the drag operation.
rls_x: x coordinate in pixels of the release point
rls_y: y coordinate in pixels of the release point
restrictions: [See the release argument to the drag method]
"""
if self._drag_idx is None:
return
start_x, start_y = self._click_start
if rls_x == start_x and rls_y == start_y:
rls_x = self._x_obj[self._drag_idx]
rls_y = self._y_obj[self._drag_idx]
else:
if self._lock_dim == 'x':
rls_x = self._x_obj[self._drag_idx]
elif self._lock_dim == 'y':
rls_y = self._y_obj[self._drag_idx]
if restrictions:
rls_x, rls_y = restrictions(rls_x, rls_y)
self._x_obj[self._drag_idx] = rls_x
self._y_obj[self._drag_idx] = rls_y
drag_idx = self._drag_idx
self._drag_idx = None
self._save_bitmap = None
self._click_start = None
return drag_idx, rls_x, rls_y
[docs] def setBackground(self, background):
"""
Change the background pixmap.
background: A QPixmap containing the new background
"""
self._background = background
[docs] def setCoords(self, x_obj, y_obj):
"""
Change the coordinates.
x_obj: x coordinate(s) in pixels of the new object
y_obj: y coordinate(s) in pixesl of the new object
"""
self._x_obj = x_obj
self._y_obj = y_obj
if type(self._x_obj) != np.ma.MaskedArray:
self._x_obj = np.ma.array([ self._x_obj ])
if type(self._y_obj) != np.ma.MaskedArray:
self._y_obj = np.ma.array([ self._y_obj ])
[docs] def getBackground(self):
"""
Returns the current background as a QPixmap
"""
return self._background
[docs] def getCoords(self):
"""
Returns the current coordinates as numpy arrays
"""
return self._x_obj, self._y_obj
[docs] def isDragging(self):
"""
Returns a boolean specifying whether or not this object is in the
middle of a drag operation.
"""
return self._drag_idx is not None
def _saveBitmap(self, lb_x, lb_y, ub_x, ub_y):
"""
Save a section of the background image [private method]. A 5 px buffer is applied
in all directions.
lb_x: Lower bound on the x coordinate in pixels.
ub_x: Upper bound on the x coordinate in pixels.
lb_y: Lower bound on the y coordinate in pixels.
ub_y: Upper bound on the y coordinate in pixels.
"""
origin = QPoint(max(lb_x - self._drag_buffer, 0), max(lb_y - self._drag_buffer, 0))
size = QSize(ub_x - lb_x + 2 * self._drag_buffer, ub_y - lb_y + 2 * self._drag_buffer)
bmap = self._background.copy(QRect(origin, size))
self._save_bitmap = (origin, size, bmap)
def _restoreBitmap(self, qp):
"""
Restore the section of the background image saved by _saveBitmap [private method].
qp: A QPainter to use to draw the bitmap.
"""
if self._save_bitmap is not None:
(origin, size, bmap) = self._save_bitmap
qp.drawPixmap(origin, bmap, QRect(QPoint(0, 0), size))