source: sasview/src/sas/qtgui/Plotter.py @ 9f25bce

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 9f25bce was 9f25bce, checked in by Piotr Rozyczko <rozyczko@…>, 8 years ago

Towards more 1D plots responding to data change.
Minor bug fixes.

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