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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 04ac604 was 04ac604, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Don't transform data which has already been transformed.
Improved existing plot check.
SASVIEW-988

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