source: sasview/src/sas/qtgui/Plotter.py @ 29eb947

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 29eb947 was b789967, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

More minor fixes to plotting

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