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

Last change on this file since c57ecca was 7d8bebf, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Some improvements in plot handling

  • Property mode set to 100644
File size: 11.5 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        # Set the object name to satisfy the Squish object picker
131        self.canvas.setObjectName(title)
132
133    @property
134    def xLabel(self, xlabel=""):
135        """ x-label setter """
136        return self.x_label
137
138    @xLabel.setter
139    def xLabel(self, xlabel=""):
140        """ x-label setter """
141        self.x_label = r'$%s$'% xlabel
142
143    @property
144    def yLabel(self, ylabel=""):
145        """ y-label setter """
146        return self.y_label
147
148    @yLabel.setter
149    def yLabel(self, ylabel=""):
150        """ y-label setter """
151        self.y_label = r'$%s$'% ylabel
152
153    @property
154    def yscale(self):
155        """ Y-axis scale getter """
156        return self._yscale
157
158    @yscale.setter
159    def yscale(self, scale='linear'):
160        """ Y-axis scale setter """
161        self.ax.set_yscale(scale, nonposy='clip')
162        self._yscale = scale
163
164    @property
165    def xscale(self):
166        """ X-axis scale getter """
167        return self._xscale
168
169    @xscale.setter
170    def xscale(self, scale='linear'):
171        """ X-axis scale setter """
172        self.ax.set_xscale(scale)
173        self._xscale = scale
174
175    def upatePlotHelper(self):
176        """
177        Notify the plot helper about the new plot
178        """
179        # Notify the helper
180        PlotHelper.addPlot(self)
181        # Notify the listeners about a new graph
182        self.manager.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
183
184    def defaultContextMenu(self):
185        """
186        Content of the dialog-universal context menu:
187        Save, Print and Copy
188        """
189        # Actions
190        self.contextMenu.clear()
191        self.actionSaveImage = self.contextMenu.addAction("Save Image")
192        self.actionPrintImage = self.contextMenu.addAction("Print Image")
193        self.actionCopyToClipboard = self.contextMenu.addAction("Copy to Clipboard")
194        self.contextMenu.addSeparator()
195
196        # Define the callbacks
197        self.actionSaveImage.triggered.connect(self.onImageSave)
198        self.actionPrintImage.triggered.connect(self.onImagePrint)
199        self.actionCopyToClipboard.triggered.connect(self.onClipboardCopy)
200
201    def createContextMenu(self):
202        """
203        Define common context menu and associated actions for the MPL widget
204        """
205        raise NotImplementedError("Context menu method must be implemented in derived class.")
206
207    def createContextMenuQuick(self):
208        """
209        Define context menu and associated actions for the quickplot MPL widget
210        """
211        raise NotImplementedError("Context menu method must be implemented in derived class.")
212
213    def contextMenuEvent(self, event):
214        """
215        Display the context menu
216        """
217        if not self.quickplot:
218            self.createContextMenu()
219        else:
220            self.createContextMenuQuick()
221
222        event_pos = event.pos()
223        self.contextMenu.exec_(self.canvas.mapToGlobal(event_pos))
224
225    def onMplMouseUp(self, event):
226        """
227        Mouse button up callback
228        """
229        pass
230
231    def onMplMouseDown(self, event):
232        """
233        Mouse button down callback
234        """
235        pass
236
237    def onMplMouseMotion(self, event):
238        """
239        Mouse motion callback
240        """
241        pass
242
243    def onMplPick(self, event):
244        """
245        Mouse pick callback
246        """
247        pass
248
249    def onMplWheel(self, event):
250        """
251        Mouse wheel scroll callback
252        """
253        pass
254
255    def clean(self):
256        """
257        Redraw the graph
258        """
259        self.figure.delaxes(self.ax)
260        self.ax = self.figure.add_subplot(self.current_plot)
261
262    def plot(self, marker=None, linestyle=None):
263        """
264        PURE VIRTUAL
265        Plot the content of self._data
266        """
267        raise NotImplementedError("Plot method must be implemented in derived class.")
268
269    def closeEvent(self, event):
270        """
271        Overwrite the close event adding helper notification
272        """
273        # Please remove me from your database.
274        PlotHelper.deletePlot(PlotHelper.idOfPlot(self))
275
276        # Notify the listeners
277        self.manager.communicator.activeGraphsSignal.emit(PlotHelper.currentPlots())
278
279        event.accept()
280
281    def onImageSave(self):
282        """
283        Use the internal MPL method for saving to file
284        """
285        self.toolbar.save_figure()
286
287    def onImagePrint(self):
288        """
289        Display printer dialog and print the MPL widget area
290        """
291        # Define the printer
292        printer = QtGui.QPrinter()
293
294        # Display the print dialog
295        dialog = QtGui.QPrintDialog(printer)
296        dialog.setModal(True)
297        dialog.setWindowTitle("Print")
298        if dialog.exec_() != QtGui.QDialog.Accepted:
299            return
300
301        painter = QtGui.QPainter(printer)
302        # Grab the widget screenshot
303        pmap = QtGui.QPixmap.grabWidget(self)
304        # Create a label with pixmap drawn
305        printLabel = QtGui.QLabel()
306        printLabel.setPixmap(pmap)
307
308        # Print the label
309        printLabel.render(painter)
310        painter.end()
311
312    def onClipboardCopy(self):
313        """
314        Copy MPL widget area to buffer
315        """
316        bmp = QtGui.QApplication.clipboard()
317        pixmap = QtGui.QPixmap.grabWidget(self.canvas)
318        bmp.setPixmap(pixmap)
319
320    def onGridToggle(self):
321        """
322        Add/remove grid lines from MPL plot
323        """
324        self.grid_on = (not self.grid_on)
325        self.ax.grid(self.grid_on)
326        self.canvas.draw_idle()
327
328    def onWindowsTitle(self):
329        """
330        Show a dialog allowing chart title customisation
331        """
332        current_title = self.windowTitle()
333        titleWidget = WindowTitle(self, new_title=current_title)
334        result = titleWidget.exec_()
335        if result != QtGui.QDialog.Accepted:
336            return
337
338        title = titleWidget.title()
339        self.setWindowTitle(title)
340        # Notify the listeners about a new graph title
341        self.manager.communicator.activeGraphName.emit((current_title, title))
342
343    def offset_graph(self):
344        """
345        Zoom and offset the graph to the last known settings
346        """
347        for ax in self.axes:
348            if self._scale_xhi is not None and self._scale_xlo is not None:
349                ax.set_xlim(self._scale_xlo, self._scale_xhi)
350            if self._scale_yhi is not None and self._scale_ylo is not None:
351                ax.set_ylim(self._scale_ylo, self._scale_yhi)
352
353    def onDataInfo(self, plot_data):
354        """
355        Displays data info text window for the selected plot
356        """
357        if isinstance(plot_data, Data1D):
358            text_to_show = GuiUtils.retrieveData1d(plot_data)
359        else:
360            text_to_show = GuiUtils.retrieveData2d(plot_data)
361        # Hardcoded sizes to enable full width rendering with default font
362        self.txt_widget.resize(420,600)
363
364        self.txt_widget.setReadOnly(True)
365        self.txt_widget.setWindowFlags(QtCore.Qt.Window)
366        self.txt_widget.setWindowIcon(QtGui.QIcon(":/res/ball.ico"))
367        self.txt_widget.setWindowTitle("Data Info: %s" % plot_data.filename)
368        self.txt_widget.insertPlainText(text_to_show)
369
370        self.txt_widget.show()
371        # Move the slider all the way up, if present
372        vertical_scroll_bar = self.txt_widget.verticalScrollBar()
373        vertical_scroll_bar.triggerAction(QtGui.QScrollBar.SliderToMinimum)
374
375    def onSavePoints(self, plot_data):
376        """
377        Saves plot data to a file
378        """
379        if isinstance(plot_data, Data1D):
380            GuiUtils.saveData1D(plot_data)
381        else:
382            GuiUtils.saveData2D(plot_data)
Note: See TracBrowser for help on using the repository browser.