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

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

Fixed text add functionality on plots - SASVIEW-859

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