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

Last change on this file since 0239237 was 0239237, checked in by awashington, 5 years ago

The default transform for SESANS data should be linear, not log

The SESANS data is negative, so log scale can't be used.

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