[plot]1D is working with pan and zoom

This commit is contained in:
Jan Grewe 2021-02-27 17:18:32 +01:00
parent b0e42ab5f8
commit b0c020891f

View File

@ -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