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

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_opencl
Last change on this file since 4c11b2a was 4c11b2a, checked in by awashington, 5 years ago

Pull the transform from the old plot when updating a plot

This is part of jira task 1127.

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