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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 412e069e was 6280464, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

More Qt5 related fixes.

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