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

Last change on this file since 6c7ebb88 was 6c7ebb88, checked in by GitHub <noreply@…>, 5 years ago

Merge d32a594acc82ad29555832f498167b51564c75f3 into f994f188e28dca36e7823b2deb3bf2bfc351c35c

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