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

ESS_GUI
Last change on this file 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
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                self.data.xtransform = 'log10(x)'
94            if self.data.ytransform is None:
95                self.data.ytransform = 'log10(y)'
96            #Added condition to Dmax explorer from P(r) perspective
97            if self.data._xaxis == 'D_{max}':
98                self.xscale = 'linear'
99            # Transform data if required.
100            if transform and (self.data.xtransform is not None or self.data.ytransform is not None):
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)
111
112        # Shortcuts
113        ax = self.ax
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
121            # Try name first
122            try:
123                marker = dict(PlotUtilities.SHAPES)[marker]
124            except KeyError:
125                marker = list(PlotUtilities.SHAPES.values())[marker]
126
127        assert marker is not None
128        # Plot name
129        if self.data.title:
130            self.title(title=self.data.title)
131        else:
132            self.title(title=self.data.name)
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)
143        self.data.custom_color = color
144
145        markersize = self._data.markersize
146
147        # Include scaling (log vs. linear)
148        ax.set_xscale(self.xscale, nonposx='clip')
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
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]
169
170        else:
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:
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
181                line = ax.errorbar(x, y,
182                            yerr=dy,
183                            xerr=None,
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,
192                            zorder=1,
193                            picker=True)
194
195        # Update the list of data sets (plots) in chart
196        self.plot_dict[self._data.name] = self.data
197
198        self.plot_lines[self._data.name] = line
199
200        # Now add the legend with some customizations.
201
202        if self.showLegend:
203            width=_legendResize(self.canvas.size().width())
204
205            self.legend = ax.legend(loc='upper right', shadow=True, prop={'size':width})
206            if self.legend:
207                self.legend.set_picker(True)
208
209        # Current labels for axes
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)
214
215        # refresh canvas
216        self.canvas.draw_idle()
217
218    def onResize(self, event):
219        """
220        Resize the legend window/font on canvas resize
221        """
222        if not self.showLegend:
223            return
224        width = _legendResize(event.width)
225        # resize the legend to follow the canvas width.
226        self.legend.prop.set_size(width)
227
228    def createContextMenu(self):
229        """
230        Define common context menu and associated actions for the MPL widget
231        """
232        self.defaultContextMenu()
233
234        # Separate plots
235        self.addPlotsToContextMenu()
236
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
248        self.contextMenu.addSeparator()
249        self.actionWindowTitle = self.contextMenu.addAction("Window Title")
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)
258
259    def addPlotsToContextMenu(self):
260        """
261        Adds operations on all plotted sets of data to the context menu
262        """
263        for id in list(self.plot_dict.keys()):
264            plot = self.plot_dict[id]
265
266            name = plot.name if plot.name else plot.title
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')
280                self.actionLinearFit.triggered.connect(
281                                functools.partial(self.onLinearFit, id))
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')
306            self.actionModifyPlot.triggered.connect(
307                                functools.partial(self.onModifyPlot, id))
308
309    def createContextMenuQuick(self):
310        """
311        Define context menu and associated actions for the quickplot MPL widget
312        """
313        # Default actions
314        self.defaultContextMenu()
315
316        # Additional actions
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        """
329        if self.properties.exec_() == QtWidgets.QDialog.Accepted:
330            self.xLogLabel, self.yLogLabel = self.properties.getValues()
331            self.data.xtransform = self.xLogLabel
332            self.data.ytransform = self.yLogLabel
333            self.xyTransform(self.xLogLabel, self.yLogLabel)
334
335    def onAddText(self):
336        """
337        Show a dialog allowing adding custom text to the chart
338        """
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)
373            self.canvas.draw_idle()
374
375    def onRemoveText(self):
376        """
377        Remove the most recently added text
378        """
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()
384        try:
385            txt.remove()
386        except ValueError:
387            # Text got already deleted somehow
388            pass
389        self.textList.remove(txt)
390
391        self.canvas.draw_idle()
392
393    def onSetGraphRange(self):
394        """
395        Show a dialog allowing setting the chart ranges
396        """
397        # min and max of data
398        if self.setRange.exec_() == QtWidgets.QDialog.Accepted:
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()
405
406    def onResetGraphRange(self):
407        """
408        Resets the chart X and Y ranges to their original values
409        """
410        x_range = (self.data.x.min(), self.data.x.max())
411        y_range = (self.data.y.min(), self.data.y.max())
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()
416
417    def onLinearFit(self, id):
418        """
419        Creates and displays a simple linear fit for the selected plot
420        """
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)
432        fit_dialog.updatePlot.connect(self.onFitDisplay)
433        if fit_dialog.exec_() == QtWidgets.QDialog.Accepted:
434            return
435
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        """
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
447        self.removePlot(id)
448        self.plot(data=new_plot)
449
450    def onRemovePlot(self, id):
451        """
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        """
462        Deletes the selected plot from the chart
463        """
464        if id not in list(self.plot_dict.keys()):
465            return
466
467        selected_plot = self.plot_dict[id]
468
469        plot_dict = copy.deepcopy(self.plot_dict)
470
471        # Labels might have been changed
472        xl = self.ax.xaxis.label.get_text()
473        yl = self.ax.yaxis.label.get_text()
474
475        self.plot_dict = {}
476
477        mpl.pyplot.cla()
478        self.ax.cla()
479
480        for ids in plot_dict:
481            if ids != id:
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)
487        self.canvas.draw_idle()
488
489    def onFreeze(self, id):
490        """
491        Freezes the selected plot to a separate chart
492        """
493        plot = self.plot_dict[id]
494        plot = copy.deepcopy(plot)
495        self.manager.add_data(data_list={id:plot})
496        self.manager.freezeDataToItem(plot)
497
498    def onModifyPlot(self, id):
499        """
500        Allows for MPL modifications to the selected plot
501        """
502        selected_plot = self.plot_dict[id]
503        selected_line = self.plot_lines[id]
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)
518        if plotPropertiesWidget.exec_() == QtWidgets.QDialog.Accepted:
519            # Update Data1d
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()
524
525            # Redraw the plot
526            self.replacePlot(id, selected_plot)
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
542        mpl.pyplot.cla()
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
552    def xyTransform(self, xLabel="", yLabel=""):
553        """
554        Transforms x and y in View and set the scale
555        """
556        # Transform all the plots on the chart
557        for id in list(self.plot_dict.keys()):
558            current_plot = self.plot_dict[id]
559            if current_plot.id == "fit":
560                self.removePlot(id)
561                continue
562
563            new_xlabel, new_ylabel, xscale, yscale =\
564                GuiUtils.xyTransform(current_plot, xLabel, yLabel)
565            self.xscale = xscale
566            self.yscale = yscale
567
568            # Plot the updated chart
569            self.removePlot(id)
570
571            # This assignment will wrap the label in Latex "$"
572            self.xLabel = new_xlabel
573            self.yLabel = new_ylabel
574
575            self.plot(data=current_plot, transform=False)
576
577        pass # debug hook
578
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
594        self.removePlot("Fit")
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
605        self.plot(data=self.fit_result, marker='-', hide_error=True)
606
607    def onMplMouseDown(self, event):
608        """
609        Left button down and ready to drag
610        """
611        # Check that the LEFT button was pressed
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
627
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
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
658        #if self.leftdown and self.selectedText is not None:
659        if not self.leftdown or self.selectedText is None:
660            return
661        # User has clicked on text and is dragging
662        if event.inaxes is None:
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
670
671    def onMplPick(self, event):
672        """
673        On pick legend
674        """
675        legend = self.legend
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)
690
691    def onLegendMotion(self, event):
692        """
693        On legend in motion
694        """
695        ax = event.inaxes
696        if ax is None:
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
730        if ax is not None:
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
777
778class Plotter(QtWidgets.QDialog, PlotterWidget):
779    def __init__(self, parent=None, quickplot=False):
780
781        QtWidgets.QDialog.__init__(self)
782        PlotterWidget.__init__(self, parent=self, manager=parent, quickplot=quickplot)
783        icon = QtGui.QIcon()
784        icon.addPixmap(QtGui.QPixmap(":/res/ball.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
785        self.setWindowIcon(icon)
786
787
Note: See TracBrowser for help on using the repository browser.