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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 1f34e00 was 1f34e00, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Don't show the plot if no data. Removed debug assert.

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