[plot]1D is working with pan and zoom
This commit is contained in:
parent
b0e42ab5f8
commit
b0c020891f
@ -1,3 +1,4 @@
|
|||||||
|
from nixview.util import dataview
|
||||||
from nixview.util.enums import PlotterTypes
|
from nixview.util.enums import PlotterTypes
|
||||||
from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QPushButton, QSizePolicy, QSlider, QVBoxLayout, QWidget
|
from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QPushButton, QSizePolicy, QSlider, QVBoxLayout, QWidget
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt
|
from PyQt5.QtCore import pyqtSignal, Qt
|
||||||
@ -31,6 +32,11 @@ def create_label(item):
|
|||||||
|
|
||||||
|
|
||||||
class MplCanvas(FigureCanvas):
|
class MplCanvas(FigureCanvas):
|
||||||
|
""" MplCanvas extends FigureCanvasQtAgg Matplotlib backend.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
FigureCanvas ([type]): [description]
|
||||||
|
"""
|
||||||
view_changed = pyqtSignal()
|
view_changed = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None, width=5, height=4, dpi=100):
|
def __init__(self, parent=None, width=5, height=4, dpi=100):
|
||||||
@ -46,24 +52,28 @@ class MplCanvas(FigureCanvas):
|
|||||||
|
|
||||||
|
|
||||||
def on_enter_figure(self, event):
|
def on_enter_figure(self, event):
|
||||||
print('enter_figure', event.canvas.figure)
|
# print('enter_figure', event.canvas.figure)
|
||||||
# event.canvas.figure.patch.set_facecolor('red')
|
# event.canvas.figure.patch.set_facecolor('red')
|
||||||
# event.canvas.draw()
|
# event.canvas.draw()
|
||||||
|
pass
|
||||||
|
|
||||||
def on_leave_figure(self, event):
|
def on_leave_figure(self, event):
|
||||||
print('leave_figure', event.canvas.figure)
|
# print('leave_figure', event.canvas.figure)
|
||||||
# event.canvas.figure.patch.set_facecolor('grey')
|
# event.canvas.figure.patch.set_facecolor('grey')
|
||||||
# event.canvas.draw()
|
# event.canvas.draw()
|
||||||
|
pass
|
||||||
|
|
||||||
def on_enter_axes(self, event):
|
def on_enter_axes(self, event):
|
||||||
print('enter_axes', event.inaxes)
|
# print('enter_axes', event.inaxes)
|
||||||
# event.inaxes.patch.set_facecolor('yellow')
|
# event.inaxes.patch.set_facecolor('yellow')
|
||||||
# event.canvas.draw()
|
# event.canvas.draw()
|
||||||
|
pass
|
||||||
|
|
||||||
def on_leave_axes(self, event):
|
def on_leave_axes(self, event):
|
||||||
print('leave_axes', event.inaxes)
|
# print('leave_axes', event.inaxes)
|
||||||
# event.inaxes.patch.set_facecolor('white')
|
# event.inaxes.patch.set_facecolor('white')
|
||||||
# event.canvas.draw()
|
# event.canvas.draw()
|
||||||
|
pass
|
||||||
|
|
||||||
def on_pick(self, event):
|
def on_pick(self, event):
|
||||||
line = event.artist
|
line = event.artist
|
||||||
@ -81,6 +91,11 @@ class MplCanvas(FigureCanvas):
|
|||||||
|
|
||||||
|
|
||||||
class Plotter(MplCanvas):
|
class Plotter(MplCanvas):
|
||||||
|
""" Abstract class for visual display of data (plotting)
|
||||||
|
Inheriting classes need to implement the current_view and is_full_view methods
|
||||||
|
|
||||||
|
Plotter extends MplCanvas.
|
||||||
|
"""
|
||||||
def __init__(self, file_handler, item, data_view, parent=None) -> None:
|
def __init__(self, file_handler, item, data_view, parent=None) -> None:
|
||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
self._file_handler = file_handler
|
self._file_handler = file_handler
|
||||||
@ -90,9 +105,13 @@ class Plotter(MplCanvas):
|
|||||||
def current_view(self):
|
def current_view(self):
|
||||||
raise NotImplementedError("current_view is not implemented on the current plotter")
|
raise NotImplementedError("current_view is not implemented on the current plotter")
|
||||||
|
|
||||||
|
@property
|
||||||
def is_full_view(self):
|
def is_full_view(self):
|
||||||
raise NotImplementedError("is_full_view is not implemented on the current plotter")
|
raise NotImplementedError("is_full_view is not implemented on the current plotter")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_pan_horizontally(self):
|
||||||
|
raise NotImplementedError("can_pan_left is not implemented on the current plotter")
|
||||||
|
|
||||||
|
|
||||||
class EventPlotter(Plotter):
|
class EventPlotter(Plotter):
|
||||||
@ -264,17 +283,17 @@ class ImagePlotter(Plotter):
|
|||||||
|
|
||||||
|
|
||||||
class LinePlotter(Plotter):
|
class LinePlotter(Plotter):
|
||||||
|
""" LinePlotter extends and implements the Plotter class. It shows line plot data. Either single or multiple line
|
||||||
|
|
||||||
|
Args:
|
||||||
|
Plotter ([type]): [description]
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, file_handler, item, data_view, xdim=-1, parent=None):
|
def __init__(self, file_handler, item, data_view, xdim=-1, parent=None):
|
||||||
super().__init__(file_handler, item, data_view, parent)
|
super().__init__(file_handler, item, data_view, parent)
|
||||||
#self.canvas = FigureCanvas(self.figure)
|
#self.canvas = FigureCanvas(self.figure)
|
||||||
#self.toolbar = NavigationToolbar(self.canvas, self)
|
#self.toolbar = NavigationToolbar(self.canvas, self)
|
||||||
|
|
||||||
self.dimensions = self._file_handler.request_dimensions(self._item.block_id, self._item.id)
|
self.dimensions = self._file_handler.request_dimensions(self._item.block_id, self._item.id)
|
||||||
self._min_x = None
|
|
||||||
self._max_x = None
|
|
||||||
self._min_x = None
|
|
||||||
self.min_x = None
|
|
||||||
self.lines = []
|
self.lines = []
|
||||||
self.dim_count = len(self._dataview.full_shape)
|
self.dim_count = len(self._dataview.full_shape)
|
||||||
if xdim == -1:
|
if xdim == -1:
|
||||||
@ -284,15 +303,26 @@ class LinePlotter(Plotter):
|
|||||||
"Cannot plot that kind of data")
|
"Cannot plot that kind of data")
|
||||||
else:
|
else:
|
||||||
self.xdim = xdim
|
self.xdim = xdim
|
||||||
|
self._data_xmin = 0
|
||||||
|
self._data_xmax = self._dataview.current_shape[self.xdim]
|
||||||
|
|
||||||
|
self._abs_xmin = 0
|
||||||
|
self._abs_xmax = self._dataview.full_shape[self.xdim]
|
||||||
|
|
||||||
|
self._view_xmin = 0
|
||||||
|
self._view_xmax = 0
|
||||||
self.axis.callbacks.connect('xlim_changed', self.on_xlims_change)
|
self.axis.callbacks.connect('xlim_changed', self.on_xlims_change)
|
||||||
self.axis.callbacks.connect('ylim_changed', self.on_ylims_change)
|
self.axis.callbacks.connect('ylim_changed', self.on_ylims_change)
|
||||||
|
self._zoom_level = 0
|
||||||
|
self._segment_length = 0
|
||||||
|
|
||||||
def on_xlims_change(self, event_ax):
|
def on_xlims_change(self, event_ax):
|
||||||
print("updated xlims: ", event_ax.get_xlim())
|
#print("updated xlims: ", event_ax.get_xlim())
|
||||||
|
pass
|
||||||
|
|
||||||
def on_ylims_change(self, event_ax):
|
def on_ylims_change(self, event_ax):
|
||||||
|
# print("updated ylims: ", event_ax.get_ylim())
|
||||||
print("updated ylims: ", event_ax.get_ylim())
|
pass
|
||||||
|
|
||||||
def current_view(self):
|
def current_view(self):
|
||||||
cv = []
|
cv = []
|
||||||
@ -300,55 +330,87 @@ class LinePlotter(Plotter):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_full_view(self):
|
def is_full_view(self):
|
||||||
xlims = self.axis.get_xlim()
|
full = self._data_xmin == self._view_xmin and self._data_xmax == self._view_xmax
|
||||||
full = self._min_x == xlims[0] and self._max_x == xlims[-1]
|
|
||||||
full = full and self._dataview.fully_loaded
|
|
||||||
return full
|
return full
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_pan_horizontally(self):
|
||||||
|
return self.can_pan_left or self.can_pan_right
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_pan_left(self):
|
||||||
|
return self._view_xmin > self._abs_xmin
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_pan_right(self):
|
||||||
|
return self._view_xmax < self._abs_xmax
|
||||||
|
|
||||||
|
@property
|
||||||
|
def horizontal_pan_position(self):
|
||||||
|
return self._view_xmax/self._abs_xmax
|
||||||
|
|
||||||
|
def horizontal_pan_to_position(self, new_position, zoomlevel):
|
||||||
|
new_xmax = int(np.min([np.ceil(new_position * self._abs_xmax), self._abs_xmax]))
|
||||||
|
segment_length = zoomlevel * self._abs_xmax
|
||||||
|
start = np.max([0, new_xmax - segment_length])
|
||||||
|
while not self._dataview.fully_loaded and new_xmax < self._dataview.current_shape[self.xdim]:
|
||||||
|
self._dataview.request_more()
|
||||||
|
|
||||||
|
self.plot(start, zoomlevel)
|
||||||
|
|
||||||
def on_zoom_in(self, new_position):
|
def on_zoom_in(self, new_position):
|
||||||
print("plotter ZOOM In!", new_position)
|
print("plotter ZOOM In!", new_position)
|
||||||
|
|
||||||
def on_zoom_out(self, new_position):
|
def on_zoom_out(self, new_position):
|
||||||
print("plotter ZOOM out!", new_position)
|
print("plotter ZOOM out!", new_position)
|
||||||
|
|
||||||
def plot(self, maxpoints=100000):
|
def plot(self, start=0, zoomlevel=1.0):
|
||||||
self.maxpoints = maxpoints
|
if zoomlevel > 1:
|
||||||
|
zoomlevel = 1.0
|
||||||
|
self._segment_length = zoomlevel * self._abs_xmax
|
||||||
|
self._zoom_level = zoomlevel
|
||||||
if self.dim_count > 2:
|
if self.dim_count > 2:
|
||||||
return
|
return
|
||||||
if self.dim_count == 1:
|
if self.dim_count == 1:
|
||||||
self.plot_array_1d()
|
self.plot_array_1d(start)
|
||||||
else:
|
else:
|
||||||
self.plot_array_2d()
|
self.plot_array_2d(start)
|
||||||
|
|
||||||
def __draw(self, start, end):
|
def _update_abs_extremes(self, display_x_min, display_xmax):
|
||||||
if self.dim_count == 1:
|
if self._data_xmin is None or display_x_min < self._data_xmin:
|
||||||
self.__draw_1d(start, end)
|
self._data_xmin = display_x_min
|
||||||
else:
|
if self._data_xmax is None or display_xmax > self._data_xmax:
|
||||||
self.__draw_2d(start, end)
|
self._data_xmax = display_xmax
|
||||||
|
|
||||||
def _set_xlims(self, data_xmin, data_xmax):
|
def _update_current_view(self, current_xmin, current_xmax):
|
||||||
if self._min_x is None or data_xmin < self._min_x:
|
self._view_xmax = current_xmax
|
||||||
self._min_x = data_xmin
|
self._view_xmin = current_xmin
|
||||||
if self._max_x is None or data_xmax > self._max_x:
|
|
||||||
self._max_x = data_xmax
|
|
||||||
|
|
||||||
def __draw_1d(self, start, end):
|
def __draw_1d(self, start, end):
|
||||||
|
""" draw the data from start to end index.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start (int): start index in the data
|
||||||
|
end (int): end index in the data
|
||||||
|
"""
|
||||||
if start < 0:
|
if start < 0:
|
||||||
start = 0
|
start = 0
|
||||||
if end > self._dataview.current_shape[self.xdim]:
|
if end > self._dataview.current_shape[self.xdim]:
|
||||||
end = self._dataview.current_shape[self.xdim]
|
end = self._dataview.current_shape[self.xdim]
|
||||||
|
y_values = self._dataview._buffer[int(start):int(end)]
|
||||||
y = self._dataview._buffer[int(start):int(end)]
|
x_values = self._file_handler.request_axis(self._item.block_id, self._item.id, 0, len(y_values), int(start))
|
||||||
x = self._file_handler.request_axis(self._item.block_id, self._item.id, 0, len(y), start)
|
self._update_abs_extremes(start, end)
|
||||||
self._set_xlims(x[0], x[-1])
|
self._update_current_view(start, end)
|
||||||
if len(self.lines) == 0:
|
if len(self.lines) == 0:
|
||||||
l, = self.axis.plot(x, y, label=self._item.name, picker=5)
|
label = self._item.name
|
||||||
|
l, = self.axis.plot(x_values, y_values, label=label)
|
||||||
|
l.set_pickradius(5)
|
||||||
self.lines.append(l)
|
self.lines.append(l)
|
||||||
else:
|
else:
|
||||||
self.lines[0].set_ydata(y)
|
self.lines[-1].set_data(x_values[:len(y_values)], y_values)
|
||||||
self.lines[0].set_xdata(x)
|
self.figure.canvas.draw_idle()
|
||||||
|
self.axis.set_ylim([np.min(y_values), np.max(y_values)])
|
||||||
self.axis.set_xlim([x[0], x[-1]])
|
self.axis.set_xlim([x_values[0], x_values[-1]])
|
||||||
|
|
||||||
def __draw_2d(self, start, end):
|
def __draw_2d(self, start, end):
|
||||||
if start < 0:
|
if start < 0:
|
||||||
@ -359,7 +421,7 @@ class LinePlotter(Plotter):
|
|||||||
x = self._file_handler.request_axis(self._item.block_id, self._item.id, self.xdim, int(end-start), start)
|
x = self._file_handler.request_axis(self._item.block_id, self._item.id, self.xdim, int(end-start), start)
|
||||||
line_count = self._dataview.current_shape[1 - self.xdim]
|
line_count = self._dataview.current_shape[1 - self.xdim]
|
||||||
line_labels = self._file_handler.request_axis(self._item.block_id, self._item.id, 1-self.xdim, line_count, 0)
|
line_labels = self._file_handler.request_axis(self._item.block_id, self._item.id, 1-self.xdim, line_count, 0)
|
||||||
self._set_xlims(x[0], x[-1])
|
self._update_abs_extremes(x[0], x[-1])
|
||||||
|
|
||||||
for i, l in enumerate(line_labels):
|
for i, l in enumerate(line_labels):
|
||||||
if (self.xdim == 0):
|
if (self.xdim == 0):
|
||||||
@ -376,8 +438,8 @@ class LinePlotter(Plotter):
|
|||||||
|
|
||||||
self.axis.set_xlim([x[0], x[-1]])
|
self.axis.set_xlim([x[0], x[-1]])
|
||||||
|
|
||||||
def plot_array_1d(self):
|
def plot_array_1d(self, start=0):
|
||||||
self.__draw_1d(0, self.maxpoints)
|
self.__draw_1d(start, start + self._segment_length)
|
||||||
xlabel = create_label(self.dimensions[self.xdim])
|
xlabel = create_label(self.dimensions[self.xdim])
|
||||||
ylabel = create_label(self._item)
|
ylabel = create_label(self._item)
|
||||||
self.axis.set_xlabel(xlabel)
|
self.axis.set_xlabel(xlabel)
|
||||||
@ -425,7 +487,8 @@ class PlotScreen(QWidget):
|
|||||||
self.layout().addWidget(close_btn)
|
self.layout().addWidget(close_btn)
|
||||||
self._data_view = None
|
self._data_view = None
|
||||||
|
|
||||||
self.zoom_position = 0
|
self._software_slide = False
|
||||||
|
self.plotter = None
|
||||||
|
|
||||||
def _create_plot_controls(self):
|
def _create_plot_controls(self):
|
||||||
plot_controls = QGroupBox()
|
plot_controls = QGroupBox()
|
||||||
@ -437,10 +500,10 @@ class PlotScreen(QWidget):
|
|||||||
self._zoom_slider.setFixedWidth(120)
|
self._zoom_slider.setFixedWidth(120)
|
||||||
self._zoom_slider.setFixedHeight(20)
|
self._zoom_slider.setFixedHeight(20)
|
||||||
self._zoom_slider.setTickPosition(QSlider.TicksBelow)
|
self._zoom_slider.setTickPosition(QSlider.TicksBelow)
|
||||||
self._zoom_slider.setSliderPosition(50)
|
self._zoom_slider.setSliderPosition(500)
|
||||||
self._zoom_slider.setMinimum(0)
|
self._zoom_slider.setMinimum(1)
|
||||||
self._zoom_slider.setMaximum(100)
|
self._zoom_slider.setMaximum(1000)
|
||||||
self._zoom_slider.setTickInterval(25)
|
self._zoom_slider.setTickInterval(250)
|
||||||
self._zoom_slider.valueChanged.connect(self.on_zoom)
|
self._zoom_slider.valueChanged.connect(self.on_zoom)
|
||||||
|
|
||||||
self._pan_slider = QSlider(Qt.Horizontal)
|
self._pan_slider = QSlider(Qt.Horizontal)
|
||||||
@ -448,9 +511,9 @@ class PlotScreen(QWidget):
|
|||||||
self._pan_slider.setFixedHeight(20)
|
self._pan_slider.setFixedHeight(20)
|
||||||
self._pan_slider.setTickPosition(QSlider.TicksBelow)
|
self._pan_slider.setTickPosition(QSlider.TicksBelow)
|
||||||
self._pan_slider.setSliderPosition(0)
|
self._pan_slider.setSliderPosition(0)
|
||||||
self._pan_slider.setMinimum(0)
|
self._pan_slider.setMinimum(1)
|
||||||
self._pan_slider.setMaximum(100)
|
self._pan_slider.setMaximum(1000)
|
||||||
self._pan_slider.setTickInterval(25)
|
self._pan_slider.setTickInterval(250)
|
||||||
self._pan_slider.valueChanged.connect(self.on_pan)
|
self._pan_slider.valueChanged.connect(self.on_pan)
|
||||||
|
|
||||||
pl = QLabel("horiz. pos.:")
|
pl = QLabel("horiz. pos.:")
|
||||||
@ -471,23 +534,22 @@ class PlotScreen(QWidget):
|
|||||||
self.close_signal.emit()
|
self.close_signal.emit()
|
||||||
|
|
||||||
def on_zoom(self, new_position):
|
def on_zoom(self, new_position):
|
||||||
if self.zoom_position < new_position:
|
if self.plotter is None:
|
||||||
self.plotter.on_zoom_out(new_position)
|
return
|
||||||
else:
|
if self._software_slide:
|
||||||
self.plotter.on_zoom_out(new_position)
|
self._software_slide = False
|
||||||
self.zoom_position = new_position
|
return
|
||||||
|
self.plotter.horizontal_pan_to_position(self._pan_slider.sliderPosition()/1000, self._zoom_slider.sliderPosition()/1000)
|
||||||
|
|
||||||
def on_pan(self, new_position):
|
def on_pan(self, new_position):
|
||||||
|
# self._pan_slider.setEnabled(False)
|
||||||
print("pan", new_position)
|
if self._software_slide:
|
||||||
|
self._software_slide = False
|
||||||
|
return
|
||||||
|
self.plotter.horizontal_pan_to_position(new_position/1000., self._zoom_slider.sliderPosition()/1000.)
|
||||||
|
|
||||||
def on_view_changed(self):
|
def on_view_changed(self):
|
||||||
print("view changed!")
|
self._pan_slider.setEnabled(self.plotter.can_pan_horizontally)
|
||||||
print(self.plotter.current_view())
|
|
||||||
if self.plotter.is_full_view:
|
|
||||||
self._zoom_slider.setSliderPosition(100)
|
|
||||||
self._pan_slider.setEnabled(not self.plotter.is_full_view)
|
|
||||||
|
|
||||||
|
|
||||||
def plot(self, item):
|
def plot(self, item):
|
||||||
try:
|
try:
|
||||||
@ -497,13 +559,13 @@ class PlotScreen(QWidget):
|
|||||||
return
|
return
|
||||||
if self._data_view is None:
|
if self._data_view is None:
|
||||||
return
|
return
|
||||||
#while not self._data_view.fully_loaded:
|
|
||||||
# self._data_view.request_more() # TODO this is just a test, needs to be removed
|
|
||||||
if item.suggested_plotter == PlotterTypes.LinePlotter:
|
if item.suggested_plotter == PlotterTypes.LinePlotter:
|
||||||
|
zoom_slider_position = np.round(1000 / (self._data_view.full_shape[item.best_xdim] / 50000))
|
||||||
|
self._software_slide = True
|
||||||
|
self._zoom_slider.setSliderPosition(zoom_slider_position)
|
||||||
self.plotter = LinePlotter(self._file_handler, item, self._data_view)
|
self.plotter = LinePlotter(self._file_handler, item, self._data_view)
|
||||||
self.plotter.view_changed.connect(self.on_view_changed)
|
self.plotter.view_changed.connect(self.on_view_changed)
|
||||||
self._container.set_plotter(self.plotter)
|
self._container.set_plotter(self.plotter)
|
||||||
self.plotter.plot(maxpoints=10000)
|
self.plotter.plot(zoomlevel=zoom_slider_position/1000.)
|
||||||
self._zoom_slider.setSliderPosition(100)
|
self._software_slide = False
|
||||||
self.zoom_position = 100
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user