source: sasview/src/sas/qtgui/Plotting/PlotterBase.py @ 170e95d

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 170e95d was dc5ef15, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Removed qtgui dependency on sasgui and wx SASVIEW-590

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