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

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

The default transform for SESANS data should be linear, not log

The SESANS data is negative, so log scale can't be used.

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