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

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

Fixed text add functionality on plots - SASVIEW-859

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