source: sasview/src/sas/qtgui/PlotterBase.py @ bda1faa7

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 bda1faa7 was 3bdbfcc, checked in by Piotr Rozyczko <rozyczko@…>, 8 years ago

Reimplementation of the slicer functionality

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