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

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 03e04a4 was 42787fb, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Fixing issues with DataOperationUtility? calculator

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