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

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

Initial commit of Celine's Invariant Perspective work SASVIEW-52

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