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

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

Resize matplotlib legend with canvas size. SASVIEW-1000

  • 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        width = _legendResize(event.width)
223        # resize the legend to follow the canvas width.
224        self.legend.prop.set_size(width)
225
226    def createContextMenu(self):
227        """
228        Define common context menu and associated actions for the MPL widget
229        """
230        self.defaultContextMenu()
231
232        # Separate plots
233        self.addPlotsToContextMenu()
234
235        # Additional menu items
236        self.contextMenu.addSeparator()
237        self.actionAddText = self.contextMenu.addAction("Add Text")
238        self.actionRemoveText = self.contextMenu.addAction("Remove Text")
239        self.contextMenu.addSeparator()
240        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
241        self.contextMenu.addSeparator()
242        self.actionSetGraphRange = self.contextMenu.addAction("Set Graph Range")
243        self.actionResetGraphRange =\
244            self.contextMenu.addAction("Reset Graph Range")
245        # Add the title change for dialogs
246        self.contextMenu.addSeparator()
247        self.actionWindowTitle = self.contextMenu.addAction("Window Title")
248
249        # Define the callbacks
250        self.actionAddText.triggered.connect(self.onAddText)
251        self.actionRemoveText.triggered.connect(self.onRemoveText)
252        self.actionChangeScale.triggered.connect(self.onScaleChange)
253        self.actionSetGraphRange.triggered.connect(self.onSetGraphRange)
254        self.actionResetGraphRange.triggered.connect(self.onResetGraphRange)
255        self.actionWindowTitle.triggered.connect(self.onWindowsTitle)
256
257    def addPlotsToContextMenu(self):
258        """
259        Adds operations on all plotted sets of data to the context menu
260        """
261        for id in list(self.plot_dict.keys()):
262            plot = self.plot_dict[id]
263
264            name = plot.name if plot.name else plot.title
265            plot_menu = self.contextMenu.addMenu('&%s' % name)
266
267            self.actionDataInfo = plot_menu.addAction("&DataInfo")
268            self.actionDataInfo.triggered.connect(
269                                functools.partial(self.onDataInfo, plot))
270
271            self.actionSavePointsAsFile = plot_menu.addAction("&Save Points as a File")
272            self.actionSavePointsAsFile.triggered.connect(
273                                functools.partial(self.onSavePoints, plot))
274            plot_menu.addSeparator()
275
276            if plot.id != 'fit':
277                self.actionLinearFit = plot_menu.addAction('&Linear Fit')
278                self.actionLinearFit.triggered.connect(
279                                functools.partial(self.onLinearFit, id))
280                plot_menu.addSeparator()
281
282            self.actionRemovePlot = plot_menu.addAction("Remove")
283            self.actionRemovePlot.triggered.connect(
284                                functools.partial(self.onRemovePlot, id))
285
286            if not plot.is_data:
287                self.actionFreeze = plot_menu.addAction('&Freeze')
288                self.actionFreeze.triggered.connect(
289                                functools.partial(self.onFreeze, id))
290            plot_menu.addSeparator()
291
292            if plot.is_data:
293                self.actionHideError = plot_menu.addAction("Hide Error Bar")
294                if plot.dy is not None and plot.dy != []:
295                    if plot.hide_error:
296                        self.actionHideError.setText('Show Error Bar')
297                else:
298                    self.actionHideError.setEnabled(False)
299                self.actionHideError.triggered.connect(
300                                functools.partial(self.onToggleHideError, id))
301                plot_menu.addSeparator()
302
303            self.actionModifyPlot = plot_menu.addAction('&Modify Plot Property')
304            self.actionModifyPlot.triggered.connect(
305                                functools.partial(self.onModifyPlot, id))
306
307    def createContextMenuQuick(self):
308        """
309        Define context menu and associated actions for the quickplot MPL widget
310        """
311        # Default actions
312        self.defaultContextMenu()
313
314        # Additional actions
315        self.actionToggleGrid = self.contextMenu.addAction("Toggle Grid On/Off")
316        self.contextMenu.addSeparator()
317        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
318
319        # Define the callbacks
320        self.actionToggleGrid.triggered.connect(self.onGridToggle)
321        self.actionChangeScale.triggered.connect(self.onScaleChange)
322
323    def onScaleChange(self):
324        """
325        Show a dialog allowing axes rescaling
326        """
327        if self.properties.exec_() == QtWidgets.QDialog.Accepted:
328            self.xLogLabel, self.yLogLabel = self.properties.getValues()
329            self.data.xtransform = self.xLogLabel
330            self.data.ytransform = self.yLogLabel
331            self.xyTransform(self.xLogLabel, self.yLogLabel)
332
333    def onAddText(self):
334        """
335        Show a dialog allowing adding custom text to the chart
336        """
337        if self.addText.exec_() != QtWidgets.QDialog.Accepted:
338            return
339
340        # Retrieve the new text, its font and color
341        extra_text = self.addText.text()
342        extra_font = self.addText.font()
343        extra_color = self.addText.color()
344
345        # Place the text on the screen at the click location
346        pos_x = self.x_click
347        pos_y = self.y_click
348
349        # Map QFont onto MPL font
350        mpl_font = FontProperties()
351        mpl_font.set_size(int(extra_font.pointSize()))
352        mpl_font.set_family(str(extra_font.family()))
353        mpl_font.set_weight(int(extra_font.weight()))
354        # MPL style names
355        styles = ['normal', 'italic', 'oblique']
356        # QFont::Style maps directly onto the above
357        try:
358            mpl_font.set_style(styles[extra_font.style()])
359        except:
360            pass
361
362        if len(extra_text) > 0:
363            new_text = self.ax.text(pos_x,
364                                    pos_y,
365                                    extra_text,
366                                    color=extra_color,
367                                    fontproperties=mpl_font)
368
369            # Update the list of annotations
370            self.textList.append(new_text)
371            self.canvas.draw_idle()
372
373    def onRemoveText(self):
374        """
375        Remove the most recently added text
376        """
377        num_text = len(self.textList)
378        if num_text < 1:
379            return
380        txt = self.textList[num_text - 1]
381        text_remove = txt.get_text()
382        try:
383            txt.remove()
384        except ValueError:
385            # Text got already deleted somehow
386            pass
387        self.textList.remove(txt)
388
389        self.canvas.draw_idle()
390
391    def onSetGraphRange(self):
392        """
393        Show a dialog allowing setting the chart ranges
394        """
395        # min and max of data
396        if self.setRange.exec_() == QtWidgets.QDialog.Accepted:
397            x_range = self.setRange.xrange()
398            y_range = self.setRange.yrange()
399            if x_range is not None and y_range is not None:
400                self.ax.set_xlim(x_range)
401                self.ax.set_ylim(y_range)
402                self.canvas.draw_idle()
403
404    def onResetGraphRange(self):
405        """
406        Resets the chart X and Y ranges to their original values
407        """
408        x_range = (self.data.x.min(), self.data.x.max())
409        y_range = (self.data.y.min(), self.data.y.max())
410        if x_range is not None and y_range is not None:
411            self.ax.set_xlim(x_range)
412            self.ax.set_ylim(y_range)
413            self.canvas.draw_idle()
414
415    def onLinearFit(self, id):
416        """
417        Creates and displays a simple linear fit for the selected plot
418        """
419        selected_plot = self.plot_dict[id]
420
421        maxrange = (min(selected_plot.x), max(selected_plot.x))
422        fitrange = self.ax.get_xlim()
423
424        fit_dialog = LinearFit(parent=self,
425                    data=selected_plot,
426                    max_range=maxrange,
427                    fit_range=fitrange,
428                    xlabel=self.xLogLabel,
429                    ylabel=self.yLogLabel)
430        fit_dialog.updatePlot.connect(self.onFitDisplay)
431        if fit_dialog.exec_() == QtWidgets.QDialog.Accepted:
432            return
433
434    def replacePlot(self, id, new_plot):
435        """
436        Remove plot 'id' and add 'new_plot' to the chart.
437        This effectlvely refreshes the chart with changes to one of its plots
438        """
439
440        # Pull the current transform settings from the old plot
441        selected_plot = self.plot_dict[id]
442        new_plot.xtransform = selected_plot.xtransform
443        new_plot.ytransform = selected_plot.ytransform
444
445        self.removePlot(id)
446        self.plot(data=new_plot)
447
448    def onRemovePlot(self, id):
449        """
450        Responds to the plot delete action
451        """
452        self.removePlot(id)
453
454        if len(self.plot_dict) == 0:
455            # last plot: graph is empty must be the panel must be destroyed
456                self.parent.close()
457
458    def removePlot(self, id):
459        """
460        Deletes the selected plot from the chart
461        """
462        if id not in list(self.plot_dict.keys()):
463            return
464
465        selected_plot = self.plot_dict[id]
466
467        plot_dict = copy.deepcopy(self.plot_dict)
468
469        # Labels might have been changed
470        xl = self.ax.xaxis.label.get_text()
471        yl = self.ax.yaxis.label.get_text()
472
473        self.plot_dict = {}
474
475        mpl.pyplot.cla()
476        self.ax.cla()
477
478        for ids in plot_dict:
479            if ids != id:
480                self.plot(data=plot_dict[ids], hide_error=plot_dict[ids].hide_error)
481
482        # Reset the labels
483        self.ax.set_xlabel(xl)
484        self.ax.set_ylabel(yl)
485        self.canvas.draw_idle()
486
487    def onFreeze(self, id):
488        """
489        Freezes the selected plot to a separate chart
490        """
491        plot = self.plot_dict[id]
492        plot = copy.deepcopy(plot)
493        self.manager.add_data(data_list={id:plot})
494        self.manager.freezeDataToItem(plot)
495
496    def onModifyPlot(self, id):
497        """
498        Allows for MPL modifications to the selected plot
499        """
500        selected_plot = self.plot_dict[id]
501        selected_line = self.plot_lines[id]
502        # Old style color - single integer for enum color
503        # New style color - #hhhhhh
504        color = selected_plot.custom_color
505        # marker symbol and size
506        marker = selected_plot.symbol
507        marker_size = selected_plot.markersize
508        # plot name
509        legend = selected_plot.title
510
511        plotPropertiesWidget = PlotProperties(self,
512                                color=color,
513                                marker=marker,
514                                marker_size=marker_size,
515                                legend=legend)
516        if plotPropertiesWidget.exec_() == QtWidgets.QDialog.Accepted:
517            # Update Data1d
518            selected_plot.markersize = plotPropertiesWidget.markersize()
519            selected_plot.custom_color = plotPropertiesWidget.color()
520            selected_plot.symbol = plotPropertiesWidget.marker()
521            selected_plot.title = plotPropertiesWidget.legend()
522
523            # Redraw the plot
524            self.replacePlot(id, selected_plot)
525
526    def onToggleHideError(self, id):
527        """
528        Toggles hide error/show error menu item
529        """
530        selected_plot = self.plot_dict[id]
531        current = selected_plot.hide_error
532
533        # Flip the flag
534        selected_plot.hide_error = not current
535
536        plot_dict = copy.deepcopy(self.plot_dict)
537        self.plot_dict = {}
538
539        # Clean the canvas
540        mpl.pyplot.cla()
541        self.ax.cla()
542
543        # Recreate the plots but reverse the error flag for the current
544        for ids in plot_dict:
545            if ids == id:
546                self.plot(data=plot_dict[ids], hide_error=(not current))
547            else:
548                self.plot(data=plot_dict[ids], hide_error=plot_dict[ids].hide_error)               
549
550    def xyTransform(self, xLabel="", yLabel=""):
551        """
552        Transforms x and y in View and set the scale
553        """
554        # Transform all the plots on the chart
555        for id in list(self.plot_dict.keys()):
556            current_plot = self.plot_dict[id]
557            if current_plot.id == "fit":
558                self.removePlot(id)
559                continue
560
561            new_xlabel, new_ylabel, xscale, yscale =\
562                GuiUtils.xyTransform(current_plot, xLabel, yLabel)
563            self.xscale = xscale
564            self.yscale = yscale
565
566            # Plot the updated chart
567            self.removePlot(id)
568
569            # This assignment will wrap the label in Latex "$"
570            self.xLabel = new_xlabel
571            self.yLabel = new_ylabel
572
573            self.plot(data=current_plot, transform=False)
574
575        pass # debug hook
576
577    def onFitDisplay(self, fit_data):
578        """
579        Add a linear fitting line to the chart
580        """
581        # Create new data structure with fitting result
582        tempx = fit_data[0]
583        tempy = fit_data[1]
584        self.fit_result.x = []
585        self.fit_result.y = []
586        self.fit_result.x = tempx
587        self.fit_result.y = tempy
588        self.fit_result.dx = None
589        self.fit_result.dy = None
590
591        #Remove another Fit, if exists
592        self.removePlot("Fit")
593
594        self.fit_result.reset_view()
595        #self.offset_graph()
596
597        # Set plot properties
598        self.fit_result.id = 'fit'
599        self.fit_result.title = 'Fit'
600        self.fit_result.name = 'Fit'
601
602        # Plot the line
603        self.plot(data=self.fit_result, marker='-', hide_error=True)
604
605    def onMplMouseDown(self, event):
606        """
607        Left button down and ready to drag
608        """
609        # Check that the LEFT button was pressed
610        if event.button != 1:
611            return
612
613        self.leftdown = True
614        for text in self.textList:
615            if text.contains(event)[0]: # If user has clicked on text
616                self.selectedText = text
617                return
618        if event.inaxes is None:
619            return
620        try:
621            self.x_click = float(event.xdata)  # / size_x
622            self.y_click = float(event.ydata)  # / size_y
623        except:
624            self.position = None
625
626        x_str = GuiUtils.formatNumber(self.x_click)
627        y_str = GuiUtils.formatNumber(self.y_click)
628        coord_str = "x: {}, y: {}".format(x_str, y_str)
629        self.manager.communicator.statusBarUpdateSignal.emit(coord_str)
630
631    def onMplMouseUp(self, event):
632        """
633        Set the data coordinates of the click
634        """
635        self.x_click = event.xdata
636        self.y_click = event.ydata
637
638        # Check that the LEFT button was released
639        if event.button == 1:
640            self.leftdown = False
641            self.selectedText = None
642
643        #release the legend
644        if self.gotLegend == 1:
645            self.gotLegend = 0
646
647    def onMplMouseMotion(self, event):
648        """
649        Check if the left button is press and the mouse in moving.
650        Compute delta for x and y coordinates and then perform the drag
651        """
652        if self.gotLegend == 1 and self.leftdown:
653            self.onLegendMotion(event)
654            return
655
656        #if self.leftdown and self.selectedText is not None:
657        if not self.leftdown or self.selectedText is None:
658            return
659        # User has clicked on text and is dragging
660        if event.inaxes is None:
661            # User has dragged outside of axes
662            self.selectedText = None
663        else:
664            # Only move text if mouse is within axes
665            self.selectedText.set_position((event.xdata, event.ydata))
666            self.canvas.draw_idle()
667        return
668
669    def onMplPick(self, event):
670        """
671        On pick legend
672        """
673        legend = self.legend
674        if event.artist != legend:
675            return
676        # Get the box of the legend.
677        bbox = self.legend.get_window_extent()
678        # Get mouse coordinates at time of pick.
679        self.mouse_x = event.mouseevent.x
680        self.mouse_y = event.mouseevent.y
681        # Get legend coordinates at time of pick.
682        self.legend_x = bbox.xmin
683        self.legend_y = bbox.ymin
684        # Indicate we picked up the legend.
685        self.gotLegend = 1
686
687        #self.legend.legendPatch.set_alpha(0.5)
688
689    def onLegendMotion(self, event):
690        """
691        On legend in motion
692        """
693        ax = event.inaxes
694        if ax is None:
695            return
696        # Event occurred inside a plotting area
697        lo_x, hi_x = ax.get_xlim()
698        lo_y, hi_y = ax.get_ylim()
699        # How much the mouse moved.
700        x = mouse_diff_x = self.mouse_x - event.x
701        y = mouse_diff_y = self.mouse_y - event.y
702        # Put back inside
703        if x < lo_x:
704            x = lo_x
705        if x > hi_x:
706            x = hi_x
707        if y < lo_y:
708            y = lo_y
709        if y > hi_y:
710            y = hi_y
711        # Move the legend from its previous location by that same amount
712        loc_in_canvas = self.legend_x - mouse_diff_x, \
713                        self.legend_y - mouse_diff_y
714        # Transform into legend coordinate system
715        trans_axes = self.legend.parent.transAxes.inverted()
716        loc_in_norm_axes = trans_axes.transform_point(loc_in_canvas)
717        self.legend_pos_loc = tuple(loc_in_norm_axes)
718        self.legend._loc = self.legend_pos_loc
719        self.canvas.draw_idle()
720
721    def onMplWheel(self, event):
722        """
723        Process mouse wheel as zoom events
724        """
725        ax = event.inaxes
726        step = event.step
727
728        if ax is not None:
729            # Event occurred inside a plotting area
730            lo, hi = ax.get_xlim()
731            lo, hi = PlotUtilities.rescale(lo, hi, step,
732                              pt=event.xdata, scale=ax.get_xscale())
733            if not self.xscale == 'log' or lo > 0:
734                self._scale_xlo = lo
735                self._scale_xhi = hi
736                ax.set_xlim((lo, hi))
737
738            lo, hi = ax.get_ylim()
739            lo, hi = PlotUtilities.rescale(lo, hi, step, pt=event.ydata,
740                              scale=ax.get_yscale())
741            if not self.yscale == 'log' or lo > 0:
742                self._scale_ylo = lo
743                self._scale_yhi = hi
744                ax.set_ylim((lo, hi))
745        else:
746            # Check if zoom happens in the axes
747            xdata, ydata = None, None
748            x, y = event.x, event.y
749
750            for ax in self.axes:
751                insidex, _ = ax.xaxis.contains(event)
752                if insidex:
753                    xdata, _ = ax.transAxes.inverted().transform_point((x, y))
754                insidey, _ = ax.yaxis.contains(event)
755                if insidey:
756                    _, ydata = ax.transAxes.inverted().transform_point((x, y))
757            if xdata is not None:
758                lo, hi = ax.get_xlim()
759                lo, hi = PlotUtilities.rescale(lo, hi, step,
760                                  bal=xdata, scale=ax.get_xscale())
761                if not self.xscale == 'log' or lo > 0:
762                    self._scale_xlo = lo
763                    self._scale_xhi = hi
764                    ax.set_xlim((lo, hi))
765            if ydata is not None:
766                lo, hi = ax.get_ylim()
767                lo, hi = PlotUtilities.rescale(lo, hi, step, bal=ydata,
768                                  scale=ax.get_yscale())
769                if not self.yscale == 'log' or lo > 0:
770                    self._scale_ylo = lo
771                    self._scale_yhi = hi
772                    ax.set_ylim((lo, hi))
773        self.canvas.draw_idle()
774
775
776class Plotter(QtWidgets.QDialog, PlotterWidget):
777    def __init__(self, parent=None, quickplot=False):
778
779        QtWidgets.QDialog.__init__(self)
780        PlotterWidget.__init__(self, parent=self, manager=parent, quickplot=quickplot)
781        icon = QtGui.QIcon()
782        icon.addPixmap(QtGui.QPixmap(":/res/ball.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
783        self.setWindowIcon(icon)
784
785
Note: See TracBrowser for help on using the repository browser.