source: sasview/src/sas/qtgui/Plotting/PlotterBase.py @ 42787fb

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 42787fb was 42787fb, checked in by Piotr Rozyczko <rozyczko@…>, 4 years ago

Fixing issues with DataOperationUtility? calculator

  • Property mode set to 100644
File size: 11.9 KB
Line 
1import pylab
2import numpy
3
4from PyQt5 import QtCore
5from PyQt5 import QtGui
6from PyQt5 import QtWidgets, QtPrintSupport
7
8from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
9from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
10
11import matplotlib.pyplot as plt
12
13DEFAULT_CMAP = pylab.cm.jet
14from sas.qtgui.Plotting.Binder import BindArtist
15from sas.qtgui.Plotting.PlotterData import Data1D
16from sas.qtgui.Plotting.PlotterData import Data2D
17
18from sas.qtgui.Plotting.ScaleProperties import ScaleProperties
19from sas.qtgui.Plotting.WindowTitle import WindowTitle
20import sas.qtgui.Utilities.GuiUtils as GuiUtils
21import sas.qtgui.Plotting.PlotHelper as PlotHelper
22import sas.qtgui.Plotting.PlotUtilities as PlotUtilities
23
24class PlotterBase(QtWidgets.QWidget):
25    def __init__(self, parent=None, manager=None, quickplot=False):
26        super(PlotterBase, self).__init__(parent)
27
28        # Required for the communicator
29        self.manager = manager
30        self.quickplot = quickplot
31
32        #plt.style.use('ggplot')
33        #plt.style.use('seaborn-darkgrid')
34
35        # a figure instance to plot on
36        self.figure = plt.figure()
37
38        # Define canvas for the figure to be placed on
39        self.canvas = FigureCanvas(self.figure)
40
41        # ... and the toolbar with all the default MPL buttons
42        self.toolbar = NavigationToolbar(self.canvas, self)
43
44        # Simple window for data display
45        self.txt_widget = QtWidgets.QTextEdit(None)
46
47        # Set the layout and place the canvas widget in it.
48        layout = QtWidgets.QVBoxLayout()
49        # FIXME setMargin -> setContentsMargins in qt5 with 4 args
50        #layout.setContentsMargins(0)
51        layout.addWidget(self.canvas)
52
53        # 1D plotter defaults
54        self.current_plot = 111
55        self._data = [] # Original 1D/2D object
56        self._xscale = 'log'
57        self._yscale = 'log'
58        self.qx_data = []
59        self.qy_data = []
60        self.color = 0
61        self.symbol = 0
62        self.grid_on = False
63        self.scale = 'linear'
64        self.x_label = "log10(x)"
65        self.y_label = "log10(y)"
66
67        # Mouse click related
68        self._scale_xlo = None
69        self._scale_xhi = None
70        self._scale_ylo = None
71        self._scale_yhi = None
72        self.x_click = None
73        self.y_click = None
74        self.event_pos = None
75        self.leftdown = False
76        self.gotLegend = 0
77
78        self.show_legend = True
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 if xlabel else ""
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 if ylabel else ""
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.cla()
176        self.ax.set_xscale(scale)
177        self._xscale = scale
178
179    @property
180    def showLegend(self):
181        """ Legend visibility getter """
182        return self.show_legend
183
184    @showLegend.setter
185    def showLegend(self, show=True):
186        """ Legend visibility setter """
187        self.show_legend = show
188
189    def upatePlotHelper(self):
190        """
191        Notify the plot helper about the new plot
192        """
193        # Notify the helper
194        PlotHelper.addPlot(self)
195        # Notify the listeners about a new graph
196        self.manager.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
197
198    def defaultContextMenu(self):
199        """
200        Content of the dialog-universal context menu:
201        Save, Print and Copy
202        """
203        # Actions
204        self.contextMenu.clear()
205        self.actionSaveImage = self.contextMenu.addAction("Save Image")
206        self.actionPrintImage = self.contextMenu.addAction("Print Image")
207        self.actionCopyToClipboard = self.contextMenu.addAction("Copy to Clipboard")
208        self.contextMenu.addSeparator()
209
210        # Define the callbacks
211        self.actionSaveImage.triggered.connect(self.onImageSave)
212        self.actionPrintImage.triggered.connect(self.onImagePrint)
213        self.actionCopyToClipboard.triggered.connect(self.onClipboardCopy)
214
215    def createContextMenu(self):
216        """
217        Define common context menu and associated actions for the MPL widget
218        """
219        raise NotImplementedError("Context menu method must be implemented in derived class.")
220
221    def createContextMenuQuick(self):
222        """
223        Define context menu and associated actions for the quickplot MPL widget
224        """
225        raise NotImplementedError("Context menu method must be implemented in derived class.")
226
227    def contextMenuEvent(self, event):
228        """
229        Display the context menu
230        """
231        if not self.quickplot:
232            self.createContextMenu()
233        else:
234            self.createContextMenuQuick()
235
236        event_pos = event.pos()
237        self.contextMenu.exec_(self.canvas.mapToGlobal(event_pos))
238
239    def onMplMouseUp(self, event):
240        """
241        Mouse button up callback
242        """
243        pass
244
245    def onMplMouseDown(self, event):
246        """
247        Mouse button down callback
248        """
249        pass
250
251    def onMplMouseMotion(self, event):
252        """
253        Mouse motion callback
254        """
255        pass
256
257    def onMplPick(self, event):
258        """
259        Mouse pick callback
260        """
261        pass
262
263    def onMplWheel(self, event):
264        """
265        Mouse wheel scroll callback
266        """
267        pass
268
269    def clean(self):
270        """
271        Redraw the graph
272        """
273        self.figure.delaxes(self.ax)
274        self.ax = self.figure.add_subplot(self.current_plot)
275
276    def plot(self, marker=None, linestyle=None):
277        """
278        PURE VIRTUAL
279        Plot the content of self._data
280        """
281        raise NotImplementedError("Plot method must be implemented in derived class.")
282
283    def closeEvent(self, event):
284        """
285        Overwrite the close event adding helper notification
286        """
287        # Please remove me from your database.
288        PlotHelper.deletePlot(PlotHelper.idOfPlot(self))
289
290        # Notify the listeners
291        self.manager.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
292
293        event.accept()
294
295    def onImageSave(self):
296        """
297        Use the internal MPL method for saving to file
298        """
299        self.toolbar.save_figure()
300
301    def onImagePrint(self):
302        """
303        Display printer dialog and print the MPL widget area
304        """
305        # Define the printer
306        printer = QtPrintSupport.QPrinter()
307
308        # Display the print dialog
309        dialog = QtPrintSupport.QPrintDialog(printer)
310        dialog.setModal(True)
311        dialog.setWindowTitle("Print")
312        if dialog.exec_() != QtWidgets.QDialog.Accepted:
313            return
314
315        painter = QtGui.QPainter(printer)
316        # Grab the widget screenshot
317        pmap = QtGui.QPixmap(self.size())
318        self.render(pmap)
319        # Create a label with pixmap drawn
320        printLabel = QtWidgets.QLabel()
321        printLabel.setPixmap(pmap)
322
323        # Print the label
324        printLabel.render(painter)
325        painter.end()
326
327    def onClipboardCopy(self):
328        """
329        Copy MPL widget area to buffer
330        """
331        bmp = QtWidgets.QApplication.clipboard()
332        pixmap = QtGui.QPixmap(self.canvas.size())
333        self.canvas.render(pixmap)
334        bmp.setPixmap(pixmap)
335
336    def onGridToggle(self):
337        """
338        Add/remove grid lines from MPL plot
339        """
340        self.grid_on = (not self.grid_on)
341        self.ax.grid(self.grid_on)
342        self.canvas.draw_idle()
343
344    def onWindowsTitle(self):
345        """
346        Show a dialog allowing chart title customisation
347        """
348        current_title = self.windowTitle()
349        titleWidget = WindowTitle(self, new_title=current_title)
350        result = titleWidget.exec_()
351        if result != QtWidgets.QDialog.Accepted:
352            return
353
354        title = titleWidget.title()
355        self.setWindowTitle(title)
356        # Notify the listeners about a new graph title
357        self.manager.communicator.activeGraphName.emit((current_title, title))
358
359    def offset_graph(self):
360        """
361        Zoom and offset the graph to the last known settings
362        """
363        for ax in self.axes:
364            if self._scale_xhi is not None and self._scale_xlo is not None:
365                ax.set_xlim(self._scale_xlo, self._scale_xhi)
366            if self._scale_yhi is not None and self._scale_ylo is not None:
367                ax.set_ylim(self._scale_ylo, self._scale_yhi)
368
369    def onDataInfo(self, plot_data):
370        """
371        Displays data info text window for the selected plot
372        """
373        if isinstance(plot_data, Data1D):
374            text_to_show = GuiUtils.retrieveData1d(plot_data)
375        else:
376            text_to_show = GuiUtils.retrieveData2d(plot_data)
377        # Hardcoded sizes to enable full width rendering with default font
378        self.txt_widget.resize(420,600)
379
380        self.txt_widget.setReadOnly(True)
381        self.txt_widget.setWindowFlags(QtCore.Qt.Window)
382        self.txt_widget.setWindowIcon(QtGui.QIcon(":/res/ball.ico"))
383        self.txt_widget.setWindowTitle("Data Info: %s" % plot_data.filename)
384        self.txt_widget.insertPlainText(text_to_show)
385
386        self.txt_widget.show()
387        # Move the slider all the way up, if present
388        vertical_scroll_bar = self.txt_widget.verticalScrollBar()
389        vertical_scroll_bar.triggerAction(QtWidgets.QScrollBar.SliderToMinimum)
390
391    def onSavePoints(self, plot_data):
392        """
393        Saves plot data to a file
394        """
395        if isinstance(plot_data, Data1D):
396            GuiUtils.saveData1D(plot_data)
397        else:
398            GuiUtils.saveData2D(plot_data)
Note: See TracBrowser for help on using the repository browser.