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

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_sync_sascalc
Last change on this file since aea6bb7 was d9e7792, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Show Plot now restores minimized plots. SASVIEW-1221 == trac#13
Added "Minimize all plots" to Window menu
Changed draw → draw_idle to avoid weird numpy linalg errors

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