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

ESS_GUIESS_GUI_opencl
Last change on this file since f994f188 was 6edd344, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

More unit test fixes.

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