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

Last change on this file since 6138f73 was 42787fb, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Fixing issues with DataOperationUtility? calculator

  • 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        if self.showLegend:
169            self.legend = ax.legend(loc='upper right', shadow=True)
170            if self.legend:
171                self.legend.set_picker(True)
172
173        # Current labels for axes
174        if self.y_label and not is_fit:
175            ax.set_ylabel(self.y_label)
176        if self.x_label and not is_fit:
177            ax.set_xlabel(self.x_label)
178
179        # Include scaling (log vs. linear)
180        ax.set_xscale(self.xscale)
181        ax.set_yscale(self.yscale)
182
183        # define the ranges
184        self.setRange = SetGraphRange(parent=self,
185            x_range=self.ax.get_xlim(), y_range=self.ax.get_ylim())
186
187        # refresh canvas
188        self.canvas.draw_idle()
189
190    def createContextMenu(self):
191        """
192        Define common context menu and associated actions for the MPL widget
193        """
194        self.defaultContextMenu()
195
196        # Separate plots
197        self.addPlotsToContextMenu()
198
199        # Additional menu items
200        self.contextMenu.addSeparator()
201        self.actionAddText = self.contextMenu.addAction("Add Text")
202        self.actionRemoveText = self.contextMenu.addAction("Remove Text")
203        self.contextMenu.addSeparator()
204        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
205        self.contextMenu.addSeparator()
206        self.actionSetGraphRange = self.contextMenu.addAction("Set Graph Range")
207        self.actionResetGraphRange =\
208            self.contextMenu.addAction("Reset Graph Range")
209        # Add the title change for dialogs
210        #if self.parent:
211        self.contextMenu.addSeparator()
212        self.actionWindowTitle = self.contextMenu.addAction("Window Title")
213
214        # Define the callbacks
215        self.actionAddText.triggered.connect(self.onAddText)
216        self.actionRemoveText.triggered.connect(self.onRemoveText)
217        self.actionChangeScale.triggered.connect(self.onScaleChange)
218        self.actionSetGraphRange.triggered.connect(self.onSetGraphRange)
219        self.actionResetGraphRange.triggered.connect(self.onResetGraphRange)
220        self.actionWindowTitle.triggered.connect(self.onWindowsTitle)
221
222    def addPlotsToContextMenu(self):
223        """
224        Adds operations on all plotted sets of data to the context menu
225        """
226        for id in list(self.plot_dict.keys()):
227            plot = self.plot_dict[id]
228
229            name = plot.name if plot.name else plot.title
230            plot_menu = self.contextMenu.addMenu('&%s' % name)
231
232            self.actionDataInfo = plot_menu.addAction("&DataInfo")
233            self.actionDataInfo.triggered.connect(
234                                functools.partial(self.onDataInfo, plot))
235
236            self.actionSavePointsAsFile = plot_menu.addAction("&Save Points as a File")
237            self.actionSavePointsAsFile.triggered.connect(
238                                functools.partial(self.onSavePoints, plot))
239            plot_menu.addSeparator()
240
241            if plot.id != 'fit':
242                self.actionLinearFit = plot_menu.addAction('&Linear Fit')
243                self.actionLinearFit.triggered.connect(
244                                functools.partial(self.onLinearFit, id))
245                plot_menu.addSeparator()
246
247            self.actionRemovePlot = plot_menu.addAction("Remove")
248            self.actionRemovePlot.triggered.connect(
249                                functools.partial(self.onRemovePlot, id))
250
251            if not plot.is_data:
252                self.actionFreeze = plot_menu.addAction('&Freeze')
253                self.actionFreeze.triggered.connect(
254                                functools.partial(self.onFreeze, id))
255            plot_menu.addSeparator()
256
257            if plot.is_data:
258                self.actionHideError = plot_menu.addAction("Hide Error Bar")
259                if plot.dy is not None and plot.dy != []:
260                    if plot.hide_error:
261                        self.actionHideError.setText('Show Error Bar')
262                else:
263                    self.actionHideError.setEnabled(False)
264                self.actionHideError.triggered.connect(
265                                functools.partial(self.onToggleHideError, id))
266                plot_menu.addSeparator()
267
268            self.actionModifyPlot = plot_menu.addAction('&Modify Plot Property')
269            self.actionModifyPlot.triggered.connect(
270                                functools.partial(self.onModifyPlot, id))
271
272    def createContextMenuQuick(self):
273        """
274        Define context menu and associated actions for the quickplot MPL widget
275        """
276        # Default actions
277        self.defaultContextMenu()
278
279        # Additional actions
280        self.actionToggleGrid = self.contextMenu.addAction("Toggle Grid On/Off")
281        self.contextMenu.addSeparator()
282        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
283
284        # Define the callbacks
285        self.actionToggleGrid.triggered.connect(self.onGridToggle)
286        self.actionChangeScale.triggered.connect(self.onScaleChange)
287
288    def onScaleChange(self):
289        """
290        Show a dialog allowing axes rescaling
291        """
292        if self.properties.exec_() == QtWidgets.QDialog.Accepted:
293            self.xLogLabel, self.yLogLabel = self.properties.getValues()
294            self.data.xtransform = self.xLogLabel
295            self.data.ytransform = self.yLogLabel
296            self.xyTransform(self.xLogLabel, self.yLogLabel)
297
298    def onAddText(self):
299        """
300        Show a dialog allowing adding custom text to the chart
301        """
302        if self.addText.exec_() != QtWidgets.QDialog.Accepted:
303            return
304
305        # Retrieve the new text, its font and color
306        extra_text = self.addText.text()
307        extra_font = self.addText.font()
308        extra_color = self.addText.color()
309
310        # Place the text on the screen at the click location
311        pos_x = self.x_click
312        pos_y = self.y_click
313
314        # Map QFont onto MPL font
315        mpl_font = FontProperties()
316        mpl_font.set_size(int(extra_font.pointSize()))
317        mpl_font.set_family(str(extra_font.family()))
318        mpl_font.set_weight(int(extra_font.weight()))
319        # MPL style names
320        styles = ['normal', 'italic', 'oblique']
321        # QFont::Style maps directly onto the above
322        try:
323            mpl_font.set_style(styles[extra_font.style()])
324        except:
325            pass
326
327        if len(extra_text) > 0:
328            new_text = self.ax.text(pos_x,
329                                    pos_y,
330                                    extra_text,
331                                    color=extra_color,
332                                    fontproperties=mpl_font)
333
334            # Update the list of annotations
335            self.textList.append(new_text)
336            self.canvas.draw()
337
338    def onRemoveText(self):
339        """
340        Remove the most recently added text
341        """
342        num_text = len(self.textList)
343        if num_text < 1:
344            return
345        txt = self.textList[num_text - 1]
346        text_remove = txt.get_text()
347        try:
348            txt.remove()
349        except ValueError:
350            # Text got already deleted somehow
351            pass
352        self.textList.remove(txt)
353
354        self.canvas.draw_idle()
355
356    def onSetGraphRange(self):
357        """
358        Show a dialog allowing setting the chart ranges
359        """
360        # min and max of data
361        if self.setRange.exec_() == QtWidgets.QDialog.Accepted:
362            x_range = self.setRange.xrange()
363            y_range = self.setRange.yrange()
364            if x_range is not None and y_range is not None:
365                self.ax.set_xlim(x_range)
366                self.ax.set_ylim(y_range)
367                self.canvas.draw_idle()
368
369    def onResetGraphRange(self):
370        """
371        Resets the chart X and Y ranges to their original values
372        """
373        x_range = (self.data.x.min(), self.data.x.max())
374        y_range = (self.data.y.min(), self.data.y.max())
375        if x_range is not None and y_range is not None:
376            self.ax.set_xlim(x_range)
377            self.ax.set_ylim(y_range)
378            self.canvas.draw_idle()
379
380    def onLinearFit(self, id):
381        """
382        Creates and displays a simple linear fit for the selected plot
383        """
384        selected_plot = self.plot_dict[id]
385
386        maxrange = (min(selected_plot.x), max(selected_plot.x))
387        fitrange = self.ax.get_xlim()
388
389        fit_dialog = LinearFit(parent=self,
390                    data=selected_plot,
391                    max_range=maxrange,
392                    fit_range=fitrange,
393                    xlabel=self.xLogLabel,
394                    ylabel=self.yLogLabel)
395        fit_dialog.updatePlot.connect(self.onFitDisplay)
396        if fit_dialog.exec_() == QtWidgets.QDialog.Accepted:
397            return
398
399    def replacePlot(self, id, new_plot):
400        """
401        Remove plot 'id' and add 'new_plot' to the chart.
402        This effectlvely refreshes the chart with changes to one of its plots
403        """
404        self.removePlot(id)
405        self.plot(data=new_plot)
406
407    def onRemovePlot(self, id):
408        """
409        Responds to the plot delete action
410        """
411        self.removePlot(id)
412
413        if len(self.plot_dict) == 0:
414            # last plot: graph is empty must be the panel must be destroyed
415                self.parent.close()
416
417    def removePlot(self, id):
418        """
419        Deletes the selected plot from the chart
420        """
421        if id not in list(self.plot_dict.keys()):
422            return
423
424        selected_plot = self.plot_dict[id]
425
426        plot_dict = copy.deepcopy(self.plot_dict)
427
428        # Labels might have been changed
429        xl = self.ax.xaxis.label.get_text()
430        yl = self.ax.yaxis.label.get_text()
431
432        self.plot_dict = {}
433
434        plt.cla()
435        self.ax.cla()
436
437        for ids in plot_dict:
438            if ids != id:
439                self.plot(data=plot_dict[ids], hide_error=plot_dict[ids].hide_error)
440
441        # Reset the labels
442        self.ax.set_xlabel(xl)
443        self.ax.set_ylabel(yl)
444        self.canvas.draw()
445
446    def onFreeze(self, id):
447        """
448        Freezes the selected plot to a separate chart
449        """
450        plot = self.plot_dict[id]
451        self.manager.add_data(data_list=[plot])
452
453    def onModifyPlot(self, id):
454        """
455        Allows for MPL modifications to the selected plot
456        """
457        selected_plot = self.plot_dict[id]
458
459        # Old style color - single integer for enum color
460        # New style color - #hhhhhh
461        color = selected_plot.custom_color
462        # marker symbol and size
463        marker = selected_plot.symbol
464        marker_size = selected_plot.markersize
465        # plot name
466        legend = selected_plot.title
467
468        plotPropertiesWidget = PlotProperties(self,
469                                color=color,
470                                marker=marker,
471                                marker_size=marker_size,
472                                legend=legend)
473        if plotPropertiesWidget.exec_() == QtWidgets.QDialog.Accepted:
474            # Update Data1d
475            selected_plot.markersize = plotPropertiesWidget.markersize()
476            selected_plot.custom_color = plotPropertiesWidget.color()
477            selected_plot.symbol = plotPropertiesWidget.marker()
478            selected_plot.title = plotPropertiesWidget.legend()
479
480            # Redraw the plot
481            self.replacePlot(id, selected_plot)
482
483    def onToggleHideError(self, id):
484        """
485        Toggles hide error/show error menu item
486        """
487        selected_plot = self.plot_dict[id]
488        current = selected_plot.hide_error
489
490        # Flip the flag
491        selected_plot.hide_error = not current
492
493        plot_dict = copy.deepcopy(self.plot_dict)
494        self.plot_dict = {}
495
496        # Clean the canvas
497        plt.cla()
498        self.ax.cla()
499
500        # Recreate the plots but reverse the error flag for the current
501        for ids in plot_dict:
502            if ids == id:
503                self.plot(data=plot_dict[ids], hide_error=(not current))
504            else:
505                self.plot(data=plot_dict[ids], hide_error=plot_dict[ids].hide_error)               
506
507    def xyTransform(self, xLabel="", yLabel=""):
508        """
509        Transforms x and y in View and set the scale
510        """
511        # Transform all the plots on the chart
512        for id in list(self.plot_dict.keys()):
513            current_plot = self.plot_dict[id]
514            if current_plot.id == "fit":
515                self.removePlot(id)
516                continue
517
518            new_xlabel, new_ylabel, xscale, yscale =\
519                GuiUtils.xyTransform(current_plot, xLabel, yLabel)
520            self.xscale = xscale
521            self.yscale = yscale
522
523            # Plot the updated chart
524            self.removePlot(id)
525
526            # This assignment will wrap the label in Latex "$"
527            self.xLabel = new_xlabel
528            self.yLabel = new_ylabel
529            # Directly overwrite the data to avoid label reassignment
530            self._data = current_plot
531            self.plot()
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.