source: sasview/src/sas/qtgui/Plotting/Plotter.py @ b764ae5

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since b764ae5 was b764ae5, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

processEvents() helps with proper chart generation. - SASVIEW-890
Fixed weighing in fitting - SASVIEW-1017
Fixed error bars after fitting - SASVIEW-1004

  • Property mode set to 100644
File size: 25.8 KB
RevLine 
[4992ff2]1from PyQt5 import QtCore
2from PyQt5 import QtGui
3from PyQt5 import QtWidgets
4
[aadf0af1]5import functools
6import copy
[0231f93]7import matplotlib as mpl
[facf4ca]8import numpy as np
[9290b1a]9from matplotlib.font_manager import FontProperties
[dc5ef15]10from sas.qtgui.Plotting.PlotterData import Data1D
[83eb5208]11from sas.qtgui.Plotting.PlotterBase import PlotterBase
12from sas.qtgui.Plotting.AddText import AddText
13from sas.qtgui.Plotting.SetGraphRange import SetGraphRange
14from sas.qtgui.Plotting.LinearFit import LinearFit
15from sas.qtgui.Plotting.PlotProperties import PlotProperties
[bb57068]16from sas.qtgui.Plotting.ScaleProperties import ScaleProperties
[dc5ef15]17
18import sas.qtgui.Utilities.GuiUtils as GuiUtils
[83eb5208]19import sas.qtgui.Plotting.PlotUtilities as PlotUtilities
[8cb6cd6]20
[416fa8f]21class PlotterWidget(PlotterBase):
[c4e5400]22    """
23    1D Plot widget for use with a QDialog
[fecfe28]24    """
[416fa8f]25    def __init__(self, parent=None, manager=None, quickplot=False):
26        super(PlotterWidget, self).__init__(parent, manager=manager, quickplot=quickplot)
[570a58f9]27
[27313b7]28        self.parent = parent
[8cb6cd6]29
[aadf0af1]30        # Dictionary of {plot_id:Data1d}
31        self.plot_dict = {}
32
[fed94a2]33        # Window for text add
34        self.addText = AddText(self)
[aadf0af1]35
[fed94a2]36        # Log-ness of the axes
[570a58f9]37        self.xLogLabel = "log10(x)"
38        self.yLogLabel = "log10(y)"
39
40        # Data container for the linear fit
[fed94a2]41        self.fit_result = Data1D(x=[], y=[], dy=None)
42        self.fit_result.symbol = 13
43        self.fit_result.name = "Fit"
[570a58f9]44
[31c5b58]45    @property
46    def data(self):
47        return self._data
48
49    @data.setter
50    def data(self, value):
[8cb6cd6]51        """ data setter """
[31c5b58]52        self._data = value
[cb4d219]53        if value._xunit:
54            self.xLabel = "%s(%s)"%(value._xaxis, value._xunit)
55        else:
56            self.xLabel = "%s"%(value._xaxis)
57        if value._yunit:
58            self.yLabel = "%s(%s)"%(value._yaxis, value._yunit)
59        else:
60            self.yLabel = "%s"%(value._yaxis)
61
62        if value.scale == 'linear' or value.isSesans:
[749b715]63            self.xscale = 'linear'
64            self.yscale = 'linear'
[aadf0af1]65        self.title(title=value.name)
[8cb6cd6]66
[04ac604]67    def plot(self, data=None, color=None, marker=None, hide_error=False, transform=True):
[8cb6cd6]68        """
[aadf0af1]69        Add a new plot of self._data to the chart.
[8cb6cd6]70        """
[9290b1a]71        # Data1D
72        if isinstance(data, Data1D):
73            self.data = data
74        assert(self._data)
75
[87cc73a]76        is_fit = (self.data.id=="fit")
[570a58f9]77
[685e0e3]78        if not is_fit:
79            # make sure we have some function to operate on
80            if self.data.xtransform is None:
81                self.data.xtransform = 'log10(x)'
82            if self.data.ytransform is None:
83                self.data.ytransform = 'log10(y)'
84
85            # Transform data if required.
[04ac604]86            if transform and (self.data.xtransform is not None or self.data.ytransform is not None):
[685e0e3]87                _, _, xscale, yscale = GuiUtils.xyTransform(self.data, self.data.xtransform, self.data.ytransform)
88                if xscale != 'log':
89                    self.xscale = xscale
90                if yscale != 'log':
91                    self.yscale = yscale
92
93                # Redefine the Scale properties dialog
94                self.properties = ScaleProperties(self,
95                                        init_scale_x=self.data.xtransform,
96                                        init_scale_y=self.data.ytransform)
[f182f93]97
[87cc73a]98        # Shortcuts
[ef01be4]99        ax = self.ax
[87cc73a]100        x = self._data.view.x
101        y = self._data.view.y
102
103        # Marker symbol. Passed marker is one of matplotlib.markers characters
104        # Alternatively, picked up from Data1D as an int index of PlotUtilities.SHAPES dict
105        if marker is None:
106            marker = self.data.symbol
[6fd4e36]107            # Try name first
108            try:
[cb4d219]109                marker = dict(PlotUtilities.SHAPES)[marker]
[6fd4e36]110            except KeyError:
[8f83719f]111                marker = list(PlotUtilities.SHAPES.values())[marker]
[87cc73a]112
[6fd4e36]113        assert marker is not None
[87cc73a]114        # Plot name
[b789967]115        if self.data.title:
116            self.title(title=self.data.title)
117        else:
118            self.title(title=self.data.name)
[87cc73a]119
120        # Error marker toggle
121        if hide_error is None:
122            hide_error = self.data.hide_error
123
124        # Plot color
125        if color is None:
126            color = self.data.custom_color
127
128        color = PlotUtilities.getValidColor(color)
[0231f93]129        self.data.custom_color = color
[87cc73a]130
131        markersize = self._data.markersize
132
[facf4ca]133        # Include scaling (log vs. linear)
[8274471e]134        ax.set_xscale(self.xscale, nonposx='clip')
[facf4ca]135        ax.set_yscale(self.yscale, nonposy='clip')
136
137        # define the ranges
138        self.setRange = SetGraphRange(parent=self,
139            x_range=self.ax.get_xlim(), y_range=self.ax.get_ylim())
140
[239214f]141        # Draw non-standard markers
142        l_width = markersize * 0.4
143        if marker == '-' or marker == '--':
144            line = self.ax.plot(x, y, color=color, lw=l_width, marker='',
145                             linestyle=marker, label=self._title, zorder=10)[0]
146
147        elif marker == 'vline':
148            y_min = min(y)*9.0/10.0 if min(y) < 0 else 0.0
149            line = self.ax.vlines(x=x, ymin=y_min, ymax=y, color=color,
150                            linestyle='-', label=self._title, lw=l_width, zorder=1)
151
152        elif marker == 'step':
153            line = self.ax.step(x, y, color=color, marker='', linestyle='-',
154                                label=self._title, lw=l_width, zorder=1)[0]
[8cb6cd6]155
[c4e5400]156        else:
[87cc73a]157            # plot data with/without errorbars
158            if hide_error:
159                line = ax.plot(x, y, marker=marker, color=color, markersize=markersize,
160                        linestyle='', label=self._title, picker=True)
161            else:
[facf4ca]162                dy = self._data.view.dy
163                # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
164                if dy is not None and type(dy) == type(()):
165                    dy = np.vstack((y - dy[0], dy[1] - y)).transpose()
166
[87cc73a]167                line = ax.errorbar(x, y,
[facf4ca]168                            yerr=dy,
169                            xerr=None,
[87cc73a]170                            capsize=2, linestyle='',
171                            barsabove=False,
172                            color=color,
173                            marker=marker,
174                            markersize=markersize,
175                            lolims=False, uplims=False,
176                            xlolims=False, xuplims=False,
177                            label=self._title,
[facf4ca]178                            zorder=1,
[87cc73a]179                            picker=True)
[8cb6cd6]180
[aadf0af1]181        # Update the list of data sets (plots) in chart
182        self.plot_dict[self._data.id] = self.data
183
[8cb6cd6]184        # Now add the legend with some customizations.
[f0bb711]185
[42787fb]186        if self.showLegend:
187            self.legend = ax.legend(loc='upper right', shadow=True)
188            if self.legend:
189                self.legend.set_picker(True)
[8cb6cd6]190
[6d05e1d]191        # Current labels for axes
[570a58f9]192        if self.y_label and not is_fit:
193            ax.set_ylabel(self.y_label)
194        if self.x_label and not is_fit:
195            ax.set_xlabel(self.x_label)
[6d05e1d]196
[8cb6cd6]197        # refresh canvas
[fbfc488]198        self.canvas.draw_idle()
[b764ae5]199        # This is an important processEvent.
200        # This allows charts to be properly updated in order
201        # of plots being applied.
202        QtWidgets.QApplication.processEvents()
[8cb6cd6]203
[aadf0af1]204    def createContextMenu(self):
[c4e5400]205        """
206        Define common context menu and associated actions for the MPL widget
207        """
208        self.defaultContextMenu()
209
[aadf0af1]210        # Separate plots
211        self.addPlotsToContextMenu()
212
[27313b7]213        # Additional menu items
214        self.contextMenu.addSeparator()
215        self.actionAddText = self.contextMenu.addAction("Add Text")
216        self.actionRemoveText = self.contextMenu.addAction("Remove Text")
217        self.contextMenu.addSeparator()
218        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
219        self.contextMenu.addSeparator()
220        self.actionSetGraphRange = self.contextMenu.addAction("Set Graph Range")
221        self.actionResetGraphRange =\
222            self.contextMenu.addAction("Reset Graph Range")
223        # Add the title change for dialogs
[aadf0af1]224        #if self.parent:
225        self.contextMenu.addSeparator()
226        self.actionWindowTitle = self.contextMenu.addAction("Window Title")
[27313b7]227
228        # Define the callbacks
229        self.actionAddText.triggered.connect(self.onAddText)
230        self.actionRemoveText.triggered.connect(self.onRemoveText)
231        self.actionChangeScale.triggered.connect(self.onScaleChange)
232        self.actionSetGraphRange.triggered.connect(self.onSetGraphRange)
233        self.actionResetGraphRange.triggered.connect(self.onResetGraphRange)
234        self.actionWindowTitle.triggered.connect(self.onWindowsTitle)
[c4e5400]235
[aadf0af1]236    def addPlotsToContextMenu(self):
237        """
238        Adds operations on all plotted sets of data to the context menu
239        """
[b3e8629]240        for id in list(self.plot_dict.keys()):
[aadf0af1]241            plot = self.plot_dict[id]
[b789967]242
243            name = plot.name if plot.name else plot.title
[aadf0af1]244            plot_menu = self.contextMenu.addMenu('&%s' % name)
245
246            self.actionDataInfo = plot_menu.addAction("&DataInfo")
247            self.actionDataInfo.triggered.connect(
248                                functools.partial(self.onDataInfo, plot))
249
250            self.actionSavePointsAsFile = plot_menu.addAction("&Save Points as a File")
251            self.actionSavePointsAsFile.triggered.connect(
252                                functools.partial(self.onSavePoints, plot))
253            plot_menu.addSeparator()
254
255            if plot.id != 'fit':
256                self.actionLinearFit = plot_menu.addAction('&Linear Fit')
[570a58f9]257                self.actionLinearFit.triggered.connect(
258                                functools.partial(self.onLinearFit, id))
[aadf0af1]259                plot_menu.addSeparator()
260
261            self.actionRemovePlot = plot_menu.addAction("Remove")
262            self.actionRemovePlot.triggered.connect(
263                                functools.partial(self.onRemovePlot, id))
264
265            if not plot.is_data:
266                self.actionFreeze = plot_menu.addAction('&Freeze')
267                self.actionFreeze.triggered.connect(
268                                functools.partial(self.onFreeze, id))
269            plot_menu.addSeparator()
270
271            if plot.is_data:
272                self.actionHideError = plot_menu.addAction("Hide Error Bar")
273                if plot.dy is not None and plot.dy != []:
274                    if plot.hide_error:
275                        self.actionHideError.setText('Show Error Bar')
276                else:
277                    self.actionHideError.setEnabled(False)
278                self.actionHideError.triggered.connect(
279                                functools.partial(self.onToggleHideError, id))
280                plot_menu.addSeparator()
281
282            self.actionModifyPlot = plot_menu.addAction('&Modify Plot Property')
[87cc73a]283            self.actionModifyPlot.triggered.connect(
284                                functools.partial(self.onModifyPlot, id))
[aadf0af1]285
286    def createContextMenuQuick(self):
[6d05e1d]287        """
288        Define context menu and associated actions for the quickplot MPL widget
289        """
[c4e5400]290        # Default actions
291        self.defaultContextMenu()
292
293        # Additional actions
[6d05e1d]294        self.actionToggleGrid = self.contextMenu.addAction("Toggle Grid On/Off")
295        self.contextMenu.addSeparator()
296        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
297
298        # Define the callbacks
299        self.actionToggleGrid.triggered.connect(self.onGridToggle)
300        self.actionChangeScale.triggered.connect(self.onScaleChange)
301
302    def onScaleChange(self):
303        """
304        Show a dialog allowing axes rescaling
305        """
[4992ff2]306        if self.properties.exec_() == QtWidgets.QDialog.Accepted:
[570a58f9]307            self.xLogLabel, self.yLogLabel = self.properties.getValues()
[bb57068]308            self.data.xtransform = self.xLogLabel
309            self.data.ytransform = self.yLogLabel
[570a58f9]310            self.xyTransform(self.xLogLabel, self.yLogLabel)
[6d05e1d]311
[27313b7]312    def onAddText(self):
313        """
314        Show a dialog allowing adding custom text to the chart
315        """
[eb1a386]316        if self.addText.exec_() != QtWidgets.QDialog.Accepted:
317            return
318
319        # Retrieve the new text, its font and color
320        extra_text = self.addText.text()
321        extra_font = self.addText.font()
322        extra_color = self.addText.color()
323
324        # Place the text on the screen at the click location
325        pos_x = self.x_click
326        pos_y = self.y_click
327
328        # Map QFont onto MPL font
329        mpl_font = FontProperties()
330        mpl_font.set_size(int(extra_font.pointSize()))
331        mpl_font.set_family(str(extra_font.family()))
332        mpl_font.set_weight(int(extra_font.weight()))
333        # MPL style names
334        styles = ['normal', 'italic', 'oblique']
335        # QFont::Style maps directly onto the above
336        try:
337            mpl_font.set_style(styles[extra_font.style()])
338        except:
339            pass
340
341        if len(extra_text) > 0:
342            new_text = self.ax.text(pos_x,
343                                    pos_y,
344                                    extra_text,
345                                    color=extra_color,
346                                    fontproperties=mpl_font)
347
348            # Update the list of annotations
349            self.textList.append(new_text)
350            self.canvas.draw()
[27313b7]351
352    def onRemoveText(self):
353        """
[9290b1a]354        Remove the most recently added text
[27313b7]355        """
[d3ca363]356        num_text = len(self.textList)
357        if num_text < 1:
358            return
359        txt = self.textList[num_text - 1]
360        text_remove = txt.get_text()
[eb1a386]361        try:
362            txt.remove()
363        except ValueError:
364            # Text got already deleted somehow
365            pass
[d3ca363]366        self.textList.remove(txt)
367
368        self.canvas.draw_idle()
[27313b7]369
370    def onSetGraphRange(self):
371        """
372        Show a dialog allowing setting the chart ranges
373        """
[d3ca363]374        # min and max of data
[4992ff2]375        if self.setRange.exec_() == QtWidgets.QDialog.Accepted:
[257bd57]376            x_range = self.setRange.xrange()
377            y_range = self.setRange.yrange()
378            if x_range is not None and y_range is not None:
379                self.ax.set_xlim(x_range)
380                self.ax.set_ylim(y_range)
381                self.canvas.draw_idle()
[27313b7]382
383    def onResetGraphRange(self):
384        """
[d3ca363]385        Resets the chart X and Y ranges to their original values
[27313b7]386        """
[d3ca363]387        x_range = (self.data.x.min(), self.data.x.max())
388        y_range = (self.data.y.min(), self.data.y.max())
[257bd57]389        if x_range is not None and y_range is not None:
390            self.ax.set_xlim(x_range)
391            self.ax.set_ylim(y_range)
392            self.canvas.draw_idle()
[27313b7]393
[570a58f9]394    def onLinearFit(self, id):
[aadf0af1]395        """
396        Creates and displays a simple linear fit for the selected plot
397        """
[570a58f9]398        selected_plot = self.plot_dict[id]
399
400        maxrange = (min(selected_plot.x), max(selected_plot.x))
401        fitrange = self.ax.get_xlim()
402
403        fit_dialog = LinearFit(parent=self,
404                    data=selected_plot,
405                    max_range=maxrange,
406                    fit_range=fitrange,
407                    xlabel=self.xLogLabel,
408                    ylabel=self.yLogLabel)
[7969b9c]409        fit_dialog.updatePlot.connect(self.onFitDisplay)
[4992ff2]410        if fit_dialog.exec_() == QtWidgets.QDialog.Accepted:
[570a58f9]411            return
[aadf0af1]412
[87cc73a]413    def replacePlot(self, id, new_plot):
414        """
415        Remove plot 'id' and add 'new_plot' to the chart.
416        This effectlvely refreshes the chart with changes to one of its plots
417        """
418        self.removePlot(id)
419        self.plot(data=new_plot)
420
[aadf0af1]421    def onRemovePlot(self, id):
422        """
[570a58f9]423        Responds to the plot delete action
424        """
425        self.removePlot(id)
426
427        if len(self.plot_dict) == 0:
428            # last plot: graph is empty must be the panel must be destroyed
429                self.parent.close()
430
431    def removePlot(self, id):
432        """
[aadf0af1]433        Deletes the selected plot from the chart
434        """
[b3e8629]435        if id not in list(self.plot_dict.keys()):
[570a58f9]436            return
437
[aadf0af1]438        selected_plot = self.plot_dict[id]
439
440        plot_dict = copy.deepcopy(self.plot_dict)
441
[b46f285]442        # Labels might have been changed
443        xl = self.ax.xaxis.label.get_text()
444        yl = self.ax.yaxis.label.get_text()
445
[aadf0af1]446        self.plot_dict = {}
447
[0231f93]448        mpl.pyplot.cla()
[aadf0af1]449        self.ax.cla()
450
451        for ids in plot_dict:
452            if ids != id:
[b46f285]453                self.plot(data=plot_dict[ids], hide_error=plot_dict[ids].hide_error)
454
455        # Reset the labels
456        self.ax.set_xlabel(xl)
457        self.ax.set_ylabel(yl)
458        self.canvas.draw()
[aadf0af1]459
460    def onFreeze(self, id):
461        """
462        Freezes the selected plot to a separate chart
463        """
464        plot = self.plot_dict[id]
465        self.manager.add_data(data_list=[plot])
466
[87cc73a]467    def onModifyPlot(self, id):
[aadf0af1]468        """
469        Allows for MPL modifications to the selected plot
470        """
[87cc73a]471        selected_plot = self.plot_dict[id]
472
473        # Old style color - single integer for enum color
474        # New style color - #hhhhhh
475        color = selected_plot.custom_color
476        # marker symbol and size
477        marker = selected_plot.symbol
478        marker_size = selected_plot.markersize
479        # plot name
480        legend = selected_plot.title
481
482        plotPropertiesWidget = PlotProperties(self,
483                                color=color,
484                                marker=marker,
485                                marker_size=marker_size,
486                                legend=legend)
[4992ff2]487        if plotPropertiesWidget.exec_() == QtWidgets.QDialog.Accepted:
[87cc73a]488            # Update Data1d
[0f3c22d]489            selected_plot.markersize = plotPropertiesWidget.markersize()
490            selected_plot.custom_color = plotPropertiesWidget.color()
491            selected_plot.symbol = plotPropertiesWidget.marker()
492            selected_plot.title = plotPropertiesWidget.legend()
[87cc73a]493
494            # Redraw the plot
495            self.replacePlot(id, selected_plot)
[aadf0af1]496
497    def onToggleHideError(self, id):
498        """
499        Toggles hide error/show error menu item
500        """
501        selected_plot = self.plot_dict[id]
502        current = selected_plot.hide_error
503
504        # Flip the flag
505        selected_plot.hide_error = not current
506
507        plot_dict = copy.deepcopy(self.plot_dict)
508        self.plot_dict = {}
509
510        # Clean the canvas
[0231f93]511        mpl.pyplot.cla()
[aadf0af1]512        self.ax.cla()
513
514        # Recreate the plots but reverse the error flag for the current
515        for ids in plot_dict:
516            if ids == id:
517                self.plot(data=plot_dict[ids], hide_error=(not current))
518            else:
519                self.plot(data=plot_dict[ids], hide_error=plot_dict[ids].hide_error)               
520
[6d05e1d]521    def xyTransform(self, xLabel="", yLabel=""):
522        """
523        Transforms x and y in View and set the scale
524        """
[570a58f9]525        # Transform all the plots on the chart
[b3e8629]526        for id in list(self.plot_dict.keys()):
[570a58f9]527            current_plot = self.plot_dict[id]
528            if current_plot.id == "fit":
529                self.removePlot(id)
530                continue
[fed94a2]531
[570a58f9]532            new_xlabel, new_ylabel, xscale, yscale =\
533                GuiUtils.xyTransform(current_plot, xLabel, yLabel)
534            self.xscale = xscale
535            self.yscale = yscale
[b46f285]536
[570a58f9]537            # Plot the updated chart
538            self.removePlot(id)
[b46f285]539
540            # This assignment will wrap the label in Latex "$"
541            self.xLabel = new_xlabel
542            self.yLabel = new_ylabel
[04ac604]543
544            self.plot(data=current_plot, transform=False)
[570a58f9]545
546        pass # debug hook
547
[fed94a2]548    def onFitDisplay(self, fit_data):
549        """
550        Add a linear fitting line to the chart
551        """
552        # Create new data structure with fitting result
553        tempx = fit_data[0]
554        tempy = fit_data[1]
555        self.fit_result.x = []
556        self.fit_result.y = []
557        self.fit_result.x = tempx
558        self.fit_result.y = tempy
559        self.fit_result.dx = None
560        self.fit_result.dy = None
561
562        #Remove another Fit, if exists
563        self.removePlot("fit")
564
565        self.fit_result.reset_view()
566        #self.offset_graph()
567
568        # Set plot properties
569        self.fit_result.id = 'fit'
570        self.fit_result.title = 'Fit'
571        self.fit_result.name = 'Fit'
572
573        # Plot the line
[87cc73a]574        self.plot(data=self.fit_result, marker='-', hide_error=True)
[fed94a2]575
[3bdbfcc]576    def onMplMouseDown(self, event):
577        """
578        Left button down and ready to drag
579        """
580        # Check that the LEFT button was pressed
[0268aed]581        if event.button != 1:
582            return
583
584        self.leftdown = True
585        for text in self.textList:
586            if text.contains(event)[0]: # If user has clicked on text
587                self.selectedText = text
588                return
589        if event.inaxes is None:
590            return
591        try:
592            self.x_click = float(event.xdata)  # / size_x
593            self.y_click = float(event.ydata)  # / size_y
594        except:
595            self.position = None
[3bdbfcc]596
597    def onMplMouseUp(self, event):
598        """
599        Set the data coordinates of the click
600        """
601        self.x_click = event.xdata
602        self.y_click = event.ydata
603
604        # Check that the LEFT button was released
605        if event.button == 1:
606            self.leftdown = False
607            self.selectedText = None
608
609        #release the legend
610        if self.gotLegend == 1:
611            self.gotLegend = 0
612
613    def onMplMouseMotion(self, event):
614        """
615        Check if the left button is press and the mouse in moving.
616        Compute delta for x and y coordinates and then perform the drag
617        """
618        if self.gotLegend == 1 and self.leftdown:
619            self.onLegendMotion(event)
620            return
621
[0268aed]622        #if self.leftdown and self.selectedText is not None:
[9f25bce]623        if not self.leftdown or self.selectedText is None:
[3bdbfcc]624            return
[0268aed]625        # User has clicked on text and is dragging
[9f25bce]626        if event.inaxes is None:
[0268aed]627            # User has dragged outside of axes
628            self.selectedText = None
629        else:
630            # Only move text if mouse is within axes
631            self.selectedText.set_position((event.xdata, event.ydata))
632            self.canvas.draw_idle()
633        return
[3bdbfcc]634
635    def onMplPick(self, event):
636        """
637        On pick legend
638        """
639        legend = self.legend
[0268aed]640        if event.artist != legend:
641            return
642        # Get the box of the legend.
643        bbox = self.legend.get_window_extent()
644        # Get mouse coordinates at time of pick.
645        self.mouse_x = event.mouseevent.x
646        self.mouse_y = event.mouseevent.y
647        # Get legend coordinates at time of pick.
648        self.legend_x = bbox.xmin
649        self.legend_y = bbox.ymin
650        # Indicate we picked up the legend.
651        self.gotLegend = 1
652
653        #self.legend.legendPatch.set_alpha(0.5)
[3bdbfcc]654
655    def onLegendMotion(self, event):
656        """
657        On legend in motion
658        """
659        ax = event.inaxes
[cee5c78]660        if ax is None:
[3bdbfcc]661            return
662        # Event occurred inside a plotting area
663        lo_x, hi_x = ax.get_xlim()
664        lo_y, hi_y = ax.get_ylim()
665        # How much the mouse moved.
666        x = mouse_diff_x = self.mouse_x - event.x
667        y = mouse_diff_y = self.mouse_y - event.y
668        # Put back inside
669        if x < lo_x:
670            x = lo_x
671        if x > hi_x:
672            x = hi_x
673        if y < lo_y:
674            y = lo_y
675        if y > hi_y:
676            y = hi_y
677        # Move the legend from its previous location by that same amount
678        loc_in_canvas = self.legend_x - mouse_diff_x, \
679                        self.legend_y - mouse_diff_y
680        # Transform into legend coordinate system
681        trans_axes = self.legend.parent.transAxes.inverted()
682        loc_in_norm_axes = trans_axes.transform_point(loc_in_canvas)
683        self.legend_pos_loc = tuple(loc_in_norm_axes)
684        self.legend._loc = self.legend_pos_loc
685        # self.canvas.draw()
686        self.canvas.draw_idle()
687
688    def onMplWheel(self, event):
689        """
690        Process mouse wheel as zoom events
691        """
692        ax = event.inaxes
693        step = event.step
694
[cee5c78]695        if ax is not None:
[3bdbfcc]696            # Event occurred inside a plotting area
697            lo, hi = ax.get_xlim()
698            lo, hi = PlotUtilities.rescale(lo, hi, step,
699                              pt=event.xdata, scale=ax.get_xscale())
700            if not self.xscale == 'log' or lo > 0:
701                self._scale_xlo = lo
702                self._scale_xhi = hi
703                ax.set_xlim((lo, hi))
704
705            lo, hi = ax.get_ylim()
706            lo, hi = PlotUtilities.rescale(lo, hi, step, pt=event.ydata,
707                              scale=ax.get_yscale())
708            if not self.yscale == 'log' or lo > 0:
709                self._scale_ylo = lo
710                self._scale_yhi = hi
711                ax.set_ylim((lo, hi))
712        else:
713            # Check if zoom happens in the axes
714            xdata, ydata = None, None
715            x, y = event.x, event.y
716
717            for ax in self.axes:
718                insidex, _ = ax.xaxis.contains(event)
719                if insidex:
720                    xdata, _ = ax.transAxes.inverted().transform_point((x, y))
721                insidey, _ = ax.yaxis.contains(event)
722                if insidey:
723                    _, ydata = ax.transAxes.inverted().transform_point((x, y))
724            if xdata is not None:
725                lo, hi = ax.get_xlim()
726                lo, hi = PlotUtilities.rescale(lo, hi, step,
727                                  bal=xdata, scale=ax.get_xscale())
728                if not self.xscale == 'log' or lo > 0:
729                    self._scale_xlo = lo
730                    self._scale_xhi = hi
731                    ax.set_xlim((lo, hi))
732            if ydata is not None:
733                lo, hi = ax.get_ylim()
734                lo, hi = PlotUtilities.rescale(lo, hi, step, bal=ydata,
735                                  scale=ax.get_yscale())
736                if not self.yscale == 'log' or lo > 0:
737                    self._scale_ylo = lo
738                    self._scale_yhi = hi
739                    ax.set_ylim((lo, hi))
740        self.canvas.draw_idle()
741
[c4e5400]742
[4992ff2]743class Plotter(QtWidgets.QDialog, PlotterWidget):
[416fa8f]744    def __init__(self, parent=None, quickplot=False):
745
[4992ff2]746        QtWidgets.QDialog.__init__(self)
[aadf0af1]747        PlotterWidget.__init__(self, parent=self, manager=parent, quickplot=quickplot)
[c4e5400]748        icon = QtGui.QIcon()
749        icon.addPixmap(QtGui.QPixmap(":/res/ball.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
750        self.setWindowIcon(icon)
751
[416fa8f]752
Note: See TracBrowser for help on using the repository browser.