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

Last change on this file since 033b1f2 was 033b1f2, checked in by awashington, 5 years ago

Remove extra logging

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