"""
Double slider interactor for setting the Q range for a fit or function
"""
import numpy as np
from sas.qtgui.Plotting.PlotterData import Data1D
from sas.qtgui.Plotting.Slicers.BaseInteractor import BaseInteractor
[docs]class QRangeSlider(BaseInteractor):
"""
Draw a pair of draggable vertical lines. Each line can be linked to a GUI input.
The GUI input should update the lines and vice-versa.
"""
def __init__(self, base, axes, color='black', zorder=5, data=None):
"""
"""
BaseInteractor.__init__(self, base, axes, color=color)
assert isinstance(data, Data1D)
self.base = base
self.markers = []
self.axes = axes
self.data = data
self.connect = self.base.connect
self.x_min = np.fabs(min(self.data.x))
self.y_marker_min = self.data.y[np.where(self.data.x == self.x_min)[0][0]]
self.x_max = np.fabs(max(self.data.x))
self.y_marker_max = self.data.y[np.where(self.data.x == self.x_max)[0][-1]]
self.updateOnMove = data.slider_update_on_move
self.line_min = LineInteractor(self, axes, zorder=zorder, x=self.x_min, y=self.y_marker_min,
input=self.data.slider_low_q_input, setter=self.data.slider_low_q_setter,
getter=self.data.slider_low_q_getter)
self.line_max = LineInteractor(self, axes, zorder=zorder, x=self.x_max, y=self.y_marker_max,
input=self.data.slider_high_q_input, setter=self.data.slider_high_q_setter,
getter=self.data.slider_high_q_getter)
self.has_move = True
self.update()
[docs] def validate(self, param_name, param_value):
"""
Validate input from user
"""
return True
[docs] def set_layer(self, n):
"""
Allow adding plot to the same panel
:param n: the number of layer
"""
self.layernum = n
self.update()
[docs] def clear(self):
"""
Clear this slicer and its markers
"""
self.clear_markers()
[docs] def update(self, x=None, y=None):
"""
Draw the new roughness on the graph.
"""
self.line_min.update(x, y)
self.line_max.update(x, y)
self.base.update()
[docs] def save(self, ev):
"""
Remember the roughness for this layer and the next so that we
can restore on Esc.
"""
self.line_min.save(ev)
self.line_max.save(ev)
[docs] def restore(self, ev):
"""
Restore the roughness for this layer.
"""
self.line_max.restore(ev)
self.line_min.restore(ev)
[docs] def move(self, x, y, ev):
"""
Process move to a new position, making sure that the move is allowed.
"""
pass
[docs] def clear_markers(self):
"""
Clear each of the lines individually
"""
self.line_min.clear()
self.line_max.clear()
[docs] def draw(self):
"""
"""
self.base.draw()
[docs]class LineInteractor(BaseInteractor):
"""
Draw a single vertical line that can be dragged on a plot
"""
def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5, input=None, setter=None, getter=None):
"""
"""
BaseInteractor.__init__(self, base, axes, color=color)
self.base = base
self.markers = []
self.axes = axes
self.x = x
self.save_x = self.x
self.y_marker = y
self.save_y = self.y_marker
# Inner circle marker
self.inner_marker = self.axes.plot([self.x], [self.y_marker], linestyle='', marker='o', markersize=4,
color=self.color, alpha=0.6, pickradius=5, label=None, zorder=zorder,
visible=True)[0]
self.line = self.axes.axvline(self.x, linestyle='-', color=self.color, marker='', pickradius=5,
label=None, zorder=zorder, visible=True)
self.has_move = True
# Map GUI input to x value so slider and input update each other
self.input = input
if self.input:
self.input.textChanged.connect(self.inputChanged)
self.setter = setter if callable(setter) else None
self.getter = getter if callable(getter) else None
self.connect_markers([self.line, self.inner_marker])
self.update()
[docs] def validate(self, param_name, param_value):
"""
Validate input from user - Should never fail
"""
return True
[docs] def clear(self):
self.clear_markers()
self.remove()
[docs] def remove(self):
"""
Clear this slicer and its markers
"""
self.inner_marker.remove()
self.line.remove()
def _set_q(self, value):
"""
Call the q setter callback method if it exists
"""
self.setter(value)
def _get_q(self):
"""
Get the q value, inferring the method to get the value
"""
if self.getter:
# Separate callback method to get Q value
self.x = float(self.getter())
elif hasattr(self.input, 'text'):
# Line edit box
self.x = float(self.input.text())
elif hasattr(self.input, 'getText'):
# Text box
self.x = float(self.input.getText())
[docs] def update(self, x=None, y=None, draw=False):
"""
Update the line position on the graph.
"""
# Reset x, y -coordinates if given as parameters
if x is not None:
self.x = np.sign(self.x) * np.fabs(x)
if y is not None:
self.y_marker = y
# Draw lines and markers
self.inner_marker.set_xdata([self.x])
self.inner_marker.set_ydata([self.y_marker])
self.line.set_xdata([self.x])
if draw:
self.base.draw()
[docs] def save(self, ev):
"""
Remember the position for this line so that we can restore on Esc.
"""
self.save_x = self.x
self.save_y = self.y_marker
[docs] def restore(self, ev):
"""
Restore the position for this line
"""
self.x = self.save_x
self.y_marker = self.save_y
[docs] def move(self, x, y, ev):
"""
Process move to a new position, making sure that the move is allowed.
"""
self.has_move = True
self.x = x
if self.base.updateOnMove:
if self.setter:
self._set_q(self.x)
else:
self.input.setText(f"{self.x:.3}")
self.y_marker = self.base.data.y[(np.abs(self.base.data.x - self.x)).argmin()]
self.update(draw=self.base.updateOnMove)
[docs] def onRelease(self, ev):
"""
Update the line position when the mouse button is released
"""
if self.setter:
self._set_q(self.x)
else:
self.input.setText(f"{self.x:.3}")
self.update(draw=True)
self.moveend(ev)
return True
[docs] def clear_markers(self):
"""
Disconnect the input and clear the callbacks
"""
if self.input:
self.input.textChanged.disconnect(self.inputChanged)
self.setter = None
self.getter = None
self.input = None