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

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

Improved line colour handling for 1D data charts.

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