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

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

Corrected plot scale for residuals

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