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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since d0952de was d0952de, checked in by wojciech, 6 years ago

Enabled linear scale for Dmax plots

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