source: sasview/src/sas/qtgui/Plotting/PlotterBase.py @ 7969b9c

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 7969b9c was 7969b9c, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

More functionality conversion

  • Property mode set to 100644
File size: 11.6 KB
RevLine 
[ef01be4]1import pylab
[9290b1a]2import numpy
[ef01be4]3
[4992ff2]4from PyQt5 import QtCore
5from PyQt5 import QtGui
6from PyQt5 import QtWidgets
[ef01be4]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#
[4992ff2]12# matplotlib.use("Qt5Agg")
13from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
14from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
[ef01be4]15
16import matplotlib.pyplot as plt
17
18DEFAULT_CMAP = pylab.cm.jet
[dc5ef15]19from sas.qtgui.Plotting.Binder import BindArtist
20from sas.qtgui.Plotting.PlotterData import Data1D
21from sas.qtgui.Plotting.PlotterData import Data2D
[9290b1a]22
[83eb5208]23from sas.qtgui.Plotting.ScaleProperties import ScaleProperties
24from sas.qtgui.Plotting.WindowTitle import WindowTitle
[dc5ef15]25import sas.qtgui.Utilities.GuiUtils as GuiUtils
[83eb5208]26import sas.qtgui.Plotting.PlotHelper as PlotHelper
27import sas.qtgui.Plotting.PlotUtilities as PlotUtilities
[ef01be4]28
[4992ff2]29class PlotterBase(QtWidgets.QWidget):
[416fa8f]30    def __init__(self, parent=None, manager=None, quickplot=False):
[ef01be4]31        super(PlotterBase, self).__init__(parent)
32
33        # Required for the communicator
[416fa8f]34        self.manager = manager
[ef01be4]35        self.quickplot = quickplot
36
37        # a figure instance to plot on
38        self.figure = plt.figure()
39
[3b7b218]40        # Define canvas for the figure to be placed on
[ef01be4]41        self.canvas = FigureCanvas(self.figure)
42
[3b7b218]43        # ... and the toolbar with all the default MPL buttons
[ef01be4]44        self.toolbar = NavigationToolbar(self.canvas, self)
45
[092a3d9]46        # Simple window for data display
[4992ff2]47        self.txt_widget = QtWidgets.QTextEdit(None)
[092a3d9]48
[3b7b218]49        # Set the layout and place the canvas widget in it.
[4992ff2]50        layout = QtWidgets.QVBoxLayout()
51        # FIXME setMargin -> setContentsMargins in qt5 with 4 args
52        #layout.setContentsMargins(0)
[ef01be4]53        layout.addWidget(self.canvas)
54
[3b7b218]55        # 1D plotter defaults
[ef01be4]56        self.current_plot = 111
[6d05e1d]57        self._data = [] # Original 1D/2D object
58        self._xscale = 'log'
59        self._yscale = 'log'
[ef01be4]60        self.qx_data = []
61        self.qy_data = []
[b4b8589]62        self.color = 0
63        self.symbol = 0
[ef01be4]64        self.grid_on = False
65        self.scale = 'linear'
[6d05e1d]66        self.x_label = "log10(x)"
67        self.y_label = "log10(y)"
[ef01be4]68
[9290b1a]69        # Mouse click related
[b46f285]70        self._scale_xlo = None
71        self._scale_xhi = None
72        self._scale_ylo = None
[570a58f9]73        self._scale_yhi = None
[9290b1a]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
[3b7b218]84        # Pre-define the Scale properties dialog
85        self.properties = ScaleProperties(self,
[570a58f9]86                                init_scale_x=self.x_label,
87                                init_scale_y=self.y_label)
[3b7b218]88
[ef01be4]89        # default color map
90        self.cmap = DEFAULT_CMAP
91
[3b7b218]92        # Add the axes object -> subplot
93        # TODO: self.ax will have to be tracked and exposed
94        # to enable subplot specific operations
[ef01be4]95        self.ax = self.figure.add_subplot(self.current_plot)
[3b7b218]96
[9290b1a]97        # Remove this, DAMMIT
98        self.axes = [self.ax]
99
[3b7b218]100        # Set the background color to white
[ef01be4]101        self.canvas.figure.set_facecolor('#FFFFFF')
102
[9290b1a]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)
[d3ca363]108        self.canvas.mpl_connect('scroll_event', self.onMplWheel)
[9290b1a]109
[4992ff2]110        self.contextMenu = QtWidgets.QMenu(self)
[aadf0af1]111
[ef01be4]112        if not quickplot:
[aadf0af1]113            # Add the toolbar
[ef01be4]114            layout.addWidget(self.toolbar)
[3b7b218]115            # Notify PlotHelper about the new plot
116            self.upatePlotHelper()
[ef01be4]117
118        self.setLayout(layout)
119
120    @property
121    def data(self):
[b4b8589]122        """ data getter """
[ef01be4]123        return self._data
124
125    @data.setter
126    def data(self, data=None):
[3b7b218]127        """ Pure virtual data setter """
128        raise NotImplementedError("Data setter must be implemented in derived class.")
[ef01be4]129
130    def title(self, title=""):
131        """ title setter """
[6d05e1d]132        self._title = title
[7d8bebf]133        # Set the object name to satisfy the Squish object picker
134        self.canvas.setObjectName(title)
[ef01be4]135
[6d05e1d]136    @property
137    def xLabel(self, xlabel=""):
138        """ x-label setter """
139        return self.x_label
140
141    @xLabel.setter
[ef01be4]142    def xLabel(self, xlabel=""):
143        """ x-label setter """
144        self.x_label = r'$%s$'% xlabel
145
[6d05e1d]146    @property
147    def yLabel(self, ylabel=""):
148        """ y-label setter """
149        return self.y_label
150
151    @yLabel.setter
[ef01be4]152    def yLabel(self, ylabel=""):
153        """ y-label setter """
154        self.y_label = r'$%s$'% ylabel
155
[6d05e1d]156    @property
157    def yscale(self):
158        """ Y-axis scale getter """
159        return self._yscale
160
[ef01be4]161    @yscale.setter
[6d05e1d]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
[ef01be4]172    @xscale.setter
[6d05e1d]173    def xscale(self, scale='linear'):
174        """ X-axis scale setter """
175        self.ax.set_xscale(scale)
176        self._xscale = scale
[ef01be4]177
[3b7b218]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
[c4e5400]187    def defaultContextMenu(self):
[ef01be4]188        """
[c4e5400]189        Content of the dialog-universal context menu:
190        Save, Print and Copy
[ef01be4]191        """
192        # Actions
[aadf0af1]193        self.contextMenu.clear()
[6d05e1d]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)
[ef01be4]201        self.actionPrintImage.triggered.connect(self.onImagePrint)
202        self.actionCopyToClipboard.triggered.connect(self.onClipboardCopy)
[6d05e1d]203
[aadf0af1]204    def createContextMenu(self):
[c4e5400]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
[aadf0af1]210    def createContextMenuQuick(self):
[6d05e1d]211        """
212        Define context menu and associated actions for the quickplot MPL widget
213        """
[3b7b218]214        raise NotImplementedError("Context menu method must be implemented in derived class.")
[6d05e1d]215
216    def contextMenuEvent(self, event):
217        """
218        Display the context menu
219        """
[aadf0af1]220        if not self.quickplot:
221            self.createContextMenu()
222        else:
223            self.createContextMenuQuick()
224
[9290b1a]225        event_pos = event.pos()
226        self.contextMenu.exec_(self.canvas.mapToGlobal(event_pos))
227
[3bdbfcc]228    def onMplMouseUp(self, event):
[9290b1a]229        """
[3bdbfcc]230        Mouse button up callback
[9290b1a]231        """
[3bdbfcc]232        pass
[9290b1a]233
[3bdbfcc]234    def onMplMouseDown(self, event):
[9290b1a]235        """
[3bdbfcc]236        Mouse button down callback
[9290b1a]237        """
[3bdbfcc]238        pass
[9290b1a]239
240    def onMplMouseMotion(self, event):
241        """
[3bdbfcc]242        Mouse motion callback
[9290b1a]243        """
[3bdbfcc]244        pass
[9290b1a]245
246    def onMplPick(self, event):
247        """
[3bdbfcc]248        Mouse pick callback
[9290b1a]249        """
[3bdbfcc]250        pass
[d3ca363]251
252    def onMplWheel(self, event):
253        """
[3bdbfcc]254        Mouse wheel scroll callback
[d3ca363]255        """
[3bdbfcc]256        pass
[6d05e1d]257
[ef01be4]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        """
[3b7b218]267        PURE VIRTUAL
[ef01be4]268        Plot the content of self._data
269        """
[3b7b218]270        raise NotImplementedError("Plot method must be implemented in derived class.")
[ef01be4]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))
[7d8bebf]278
[ef01be4]279        # Notify the listeners
[416fa8f]280        self.manager.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
[7d8bebf]281
[ef01be4]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
[6d05e1d]295        printer = QtGui.QPrinter()
296
297        # Display the print dialog
298        dialog = QtGui.QPrintDialog(printer)
299        dialog.setModal(True)
300        dialog.setWindowTitle("Print")
[4992ff2]301        if dialog.exec_() != QtWidgets.QDialog.Accepted:
[ef01be4]302            return
303
304        painter = QtGui.QPainter(printer)
[b4b8589]305        # Grab the widget screenshot
[ef01be4]306        pmap = QtGui.QPixmap.grabWidget(self)
[b4b8589]307        # Create a label with pixmap drawn
[4992ff2]308        printLabel = QtWidgets.QLabel()
[ef01be4]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        """
[7969b9c]319        bmp = QtWidgets.QApplication.clipboard()
[6d05e1d]320        pixmap = QtGui.QPixmap.grabWidget(self.canvas)
321        bmp.setPixmap(pixmap)
[ef01be4]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()
[27313b7]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_()
[4992ff2]338        if result != QtWidgets.QDialog.Accepted:
[27313b7]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))
[570a58f9]345
[b46f285]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)
[092a3d9]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()
[7969b9c]376        vertical_scroll_bar.triggerAction(QtWidgets.QScrollBar.SliderToMinimum)
[092a3d9]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.