source: sasview/src/sas/qtgui/Plotting/PlotterBase.py @ 4992ff2

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 4992ff2 was 4992ff2, checked in by Piotr Rozyczko <rozyczko@…>, 4 years ago

Initial, in-progress version. Not really working atm. SASVIEW-787

  • Property mode set to 100644
File size: 11.6 KB
Line 
1import pylab
2import numpy
3
4from PyQt5 import QtCore
5from PyQt5 import QtGui
6from PyQt5 import QtWidgets
7
8# TODO: Replace the qt4agg calls below with qt5 equivalent.
9# Requires some code modifications.
10# https://www.boxcontrol.net/embedding-matplotlib-plot-on-pyqt5-gui.html
11#
12# matplotlib.use("Qt5Agg")
13from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
14from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
15
16import matplotlib.pyplot as plt
17
18DEFAULT_CMAP = pylab.cm.jet
19from sas.qtgui.Plotting.Binder import BindArtist
20from sas.qtgui.Plotting.PlotterData import Data1D
21from sas.qtgui.Plotting.PlotterData import Data2D
22
23from sas.qtgui.Plotting.ScaleProperties import ScaleProperties
24from sas.qtgui.Plotting.WindowTitle import WindowTitle
25import sas.qtgui.Utilities.GuiUtils as GuiUtils
26import sas.qtgui.Plotting.PlotHelper as PlotHelper
27import sas.qtgui.Plotting.PlotUtilities as PlotUtilities
28
29class PlotterBase(QtWidgets.QWidget):
30    def __init__(self, parent=None, manager=None, quickplot=False):
31        super(PlotterBase, self).__init__(parent)
32
33        # Required for the communicator
34        self.manager = manager
35        self.quickplot = quickplot
36
37        # a figure instance to plot on
38        self.figure = plt.figure()
39
40        # Define canvas for the figure to be placed on
41        self.canvas = FigureCanvas(self.figure)
42
43        # ... and the toolbar with all the default MPL buttons
44        self.toolbar = NavigationToolbar(self.canvas, self)
45
46        # Simple window for data display
47        self.txt_widget = QtWidgets.QTextEdit(None)
48
49        # Set the layout and place the canvas widget in it.
50        layout = QtWidgets.QVBoxLayout()
51        # FIXME setMargin -> setContentsMargins in qt5 with 4 args
52        #layout.setContentsMargins(0)
53        layout.addWidget(self.canvas)
54
55        # 1D plotter defaults
56        self.current_plot = 111
57        self._data = [] # Original 1D/2D object
58        self._xscale = 'log'
59        self._yscale = 'log'
60        self.qx_data = []
61        self.qy_data = []
62        self.color = 0
63        self.symbol = 0
64        self.grid_on = False
65        self.scale = 'linear'
66        self.x_label = "log10(x)"
67        self.y_label = "log10(y)"
68
69        # Mouse click related
70        self._scale_xlo = None
71        self._scale_xhi = None
72        self._scale_ylo = None
73        self._scale_yhi = None
74        self.x_click = None
75        self.y_click = None
76        self.event_pos = None
77        self.leftdown = False
78        self.gotLegend = 0
79
80        # Annotations
81        self.selectedText = None
82        self.textList = []
83
84        # Pre-define the Scale properties dialog
85        self.properties = ScaleProperties(self,
86                                init_scale_x=self.x_label,
87                                init_scale_y=self.y_label)
88
89        # default color map
90        self.cmap = DEFAULT_CMAP
91
92        # Add the axes object -> subplot
93        # TODO: self.ax will have to be tracked and exposed
94        # to enable subplot specific operations
95        self.ax = self.figure.add_subplot(self.current_plot)
96
97        # Remove this, DAMMIT
98        self.axes = [self.ax]
99
100        # Set the background color to white
101        self.canvas.figure.set_facecolor('#FFFFFF')
102
103        # Canvas event handlers
104        self.canvas.mpl_connect('button_release_event', self.onMplMouseUp)
105        self.canvas.mpl_connect('button_press_event', self.onMplMouseDown)
106        self.canvas.mpl_connect('motion_notify_event', self.onMplMouseMotion)
107        self.canvas.mpl_connect('pick_event', self.onMplPick)
108        self.canvas.mpl_connect('scroll_event', self.onMplWheel)
109
110        self.contextMenu = QtWidgets.QMenu(self)
111
112        if not quickplot:
113            # Add the toolbar
114            layout.addWidget(self.toolbar)
115            # Notify PlotHelper about the new plot
116            self.upatePlotHelper()
117
118        self.setLayout(layout)
119
120    @property
121    def data(self):
122        """ data getter """
123        return self._data
124
125    @data.setter
126    def data(self, data=None):
127        """ Pure virtual data setter """
128        raise NotImplementedError("Data setter must be implemented in derived class.")
129
130    def title(self, title=""):
131        """ title setter """
132        self._title = title
133        # Set the object name to satisfy the Squish object picker
134        self.canvas.setObjectName(title)
135
136    @property
137    def xLabel(self, xlabel=""):
138        """ x-label setter """
139        return self.x_label
140
141    @xLabel.setter
142    def xLabel(self, xlabel=""):
143        """ x-label setter """
144        self.x_label = r'$%s$'% xlabel
145
146    @property
147    def yLabel(self, ylabel=""):
148        """ y-label setter """
149        return self.y_label
150
151    @yLabel.setter
152    def yLabel(self, ylabel=""):
153        """ y-label setter """
154        self.y_label = r'$%s$'% ylabel
155
156    @property
157    def yscale(self):
158        """ Y-axis scale getter """
159        return self._yscale
160
161    @yscale.setter
162    def yscale(self, scale='linear'):
163        """ Y-axis scale setter """
164        self.ax.set_yscale(scale, nonposy='clip')
165        self._yscale = scale
166
167    @property
168    def xscale(self):
169        """ X-axis scale getter """
170        return self._xscale
171
172    @xscale.setter
173    def xscale(self, scale='linear'):
174        """ X-axis scale setter """
175        self.ax.set_xscale(scale)
176        self._xscale = scale
177
178    def upatePlotHelper(self):
179        """
180        Notify the plot helper about the new plot
181        """
182        # Notify the helper
183        PlotHelper.addPlot(self)
184        # Notify the listeners about a new graph
185        self.manager.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
186
187    def defaultContextMenu(self):
188        """
189        Content of the dialog-universal context menu:
190        Save, Print and Copy
191        """
192        # Actions
193        self.contextMenu.clear()
194        self.actionSaveImage = self.contextMenu.addAction("Save Image")
195        self.actionPrintImage = self.contextMenu.addAction("Print Image")
196        self.actionCopyToClipboard = self.contextMenu.addAction("Copy to Clipboard")
197        self.contextMenu.addSeparator()
198
199        # Define the callbacks
200        self.actionSaveImage.triggered.connect(self.onImageSave)
201        self.actionPrintImage.triggered.connect(self.onImagePrint)
202        self.actionCopyToClipboard.triggered.connect(self.onClipboardCopy)
203
204    def createContextMenu(self):
205        """
206        Define common context menu and associated actions for the MPL widget
207        """
208        raise NotImplementedError("Context menu method must be implemented in derived class.")
209
210    def createContextMenuQuick(self):
211        """
212        Define context menu and associated actions for the quickplot MPL widget
213        """
214        raise NotImplementedError("Context menu method must be implemented in derived class.")
215
216    def contextMenuEvent(self, event):
217        """
218        Display the context menu
219        """
220        if not self.quickplot:
221            self.createContextMenu()
222        else:
223            self.createContextMenuQuick()
224
225        event_pos = event.pos()
226        self.contextMenu.exec_(self.canvas.mapToGlobal(event_pos))
227
228    def onMplMouseUp(self, event):
229        """
230        Mouse button up callback
231        """
232        pass
233
234    def onMplMouseDown(self, event):
235        """
236        Mouse button down callback
237        """
238        pass
239
240    def onMplMouseMotion(self, event):
241        """
242        Mouse motion callback
243        """
244        pass
245
246    def onMplPick(self, event):
247        """
248        Mouse pick callback
249        """
250        pass
251
252    def onMplWheel(self, event):
253        """
254        Mouse wheel scroll callback
255        """
256        pass
257
258    def clean(self):
259        """
260        Redraw the graph
261        """
262        self.figure.delaxes(self.ax)
263        self.ax = self.figure.add_subplot(self.current_plot)
264
265    def plot(self, marker=None, linestyle=None):
266        """
267        PURE VIRTUAL
268        Plot the content of self._data
269        """
270        raise NotImplementedError("Plot method must be implemented in derived class.")
271
272    def closeEvent(self, event):
273        """
274        Overwrite the close event adding helper notification
275        """
276        # Please remove me from your database.
277        PlotHelper.deletePlot(PlotHelper.idOfPlot(self))
278
279        # Notify the listeners
280        self.manager.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
281
282        event.accept()
283
284    def onImageSave(self):
285        """
286        Use the internal MPL method for saving to file
287        """
288        self.toolbar.save_figure()
289
290    def onImagePrint(self):
291        """
292        Display printer dialog and print the MPL widget area
293        """
294        # Define the printer
295        printer = QtGui.QPrinter()
296
297        # Display the print dialog
298        dialog = QtGui.QPrintDialog(printer)
299        dialog.setModal(True)
300        dialog.setWindowTitle("Print")
301        if dialog.exec_() != QtWidgets.QDialog.Accepted:
302            return
303
304        painter = QtGui.QPainter(printer)
305        # Grab the widget screenshot
306        pmap = QtGui.QPixmap.grabWidget(self)
307        # Create a label with pixmap drawn
308        printLabel = QtWidgets.QLabel()
309        printLabel.setPixmap(pmap)
310
311        # Print the label
312        printLabel.render(painter)
313        painter.end()
314
315    def onClipboardCopy(self):
316        """
317        Copy MPL widget area to buffer
318        """
319        bmp = QtGui.QApplication.clipboard()
320        pixmap = QtGui.QPixmap.grabWidget(self.canvas)
321        bmp.setPixmap(pixmap)
322
323    def onGridToggle(self):
324        """
325        Add/remove grid lines from MPL plot
326        """
327        self.grid_on = (not self.grid_on)
328        self.ax.grid(self.grid_on)
329        self.canvas.draw_idle()
330
331    def onWindowsTitle(self):
332        """
333        Show a dialog allowing chart title customisation
334        """
335        current_title = self.windowTitle()
336        titleWidget = WindowTitle(self, new_title=current_title)
337        result = titleWidget.exec_()
338        if result != QtWidgets.QDialog.Accepted:
339            return
340
341        title = titleWidget.title()
342        self.setWindowTitle(title)
343        # Notify the listeners about a new graph title
344        self.manager.communicator.activeGraphName.emit((current_title, title))
345
346    def offset_graph(self):
347        """
348        Zoom and offset the graph to the last known settings
349        """
350        for ax in self.axes:
351            if self._scale_xhi is not None and self._scale_xlo is not None:
352                ax.set_xlim(self._scale_xlo, self._scale_xhi)
353            if self._scale_yhi is not None and self._scale_ylo is not None:
354                ax.set_ylim(self._scale_ylo, self._scale_yhi)
355
356    def onDataInfo(self, plot_data):
357        """
358        Displays data info text window for the selected plot
359        """
360        if isinstance(plot_data, Data1D):
361            text_to_show = GuiUtils.retrieveData1d(plot_data)
362        else:
363            text_to_show = GuiUtils.retrieveData2d(plot_data)
364        # Hardcoded sizes to enable full width rendering with default font
365        self.txt_widget.resize(420,600)
366
367        self.txt_widget.setReadOnly(True)
368        self.txt_widget.setWindowFlags(QtCore.Qt.Window)
369        self.txt_widget.setWindowIcon(QtGui.QIcon(":/res/ball.ico"))
370        self.txt_widget.setWindowTitle("Data Info: %s" % plot_data.filename)
371        self.txt_widget.insertPlainText(text_to_show)
372
373        self.txt_widget.show()
374        # Move the slider all the way up, if present
375        vertical_scroll_bar = self.txt_widget.verticalScrollBar()
376        vertical_scroll_bar.triggerAction(QtGui.QScrollBar.SliderToMinimum)
377
378    def onSavePoints(self, plot_data):
379        """
380        Saves plot data to a file
381        """
382        if isinstance(plot_data, Data1D):
383            GuiUtils.saveData1D(plot_data)
384        else:
385            GuiUtils.saveData2D(plot_data)
Note: See TracBrowser for help on using the repository browser.