source: sasview/src/sas/qtgui/Plotter.py @ 257bd57

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

unit tests for graph range and add text

  • Property mode set to 100644
File size: 13.1 KB
RevLine 
[8cb6cd6]1from PyQt4 import QtGui
2
3import matplotlib.pyplot as plt
[9290b1a]4from matplotlib.font_manager import FontProperties
[8cb6cd6]5
[9290b1a]6from sas.sasgui.guiframe.dataFitting import Data1D
[6d05e1d]7from sas.sasgui.plottools import transform
8from sas.sasgui.plottools.convert_units import convert_unit
[ef01be4]9from sas.qtgui.PlotterBase import PlotterBase
[9290b1a]10from sas.qtgui.AddText import AddText
[d3ca363]11from sas.qtgui.SetGraphRange import SetGraphRange
[8cb6cd6]12
[416fa8f]13class PlotterWidget(PlotterBase):
[c4e5400]14    """
15    1D Plot widget for use with a QDialog
[fecfe28]16    """
[416fa8f]17    def __init__(self, parent=None, manager=None, quickplot=False):
18        super(PlotterWidget, self).__init__(parent, manager=manager, quickplot=quickplot)
[27313b7]19        self.parent = parent
[257bd57]20        self.addText = AddText(self)
[8cb6cd6]21
[31c5b58]22    @property
23    def data(self):
24        return self._data
25
26    @data.setter
27    def data(self, value):
[8cb6cd6]28        """ data setter """
[31c5b58]29        self._data = value
[6d05e1d]30        self.xLabel = "%s(%s)"%(value._xaxis, value._xunit)
31        self.yLabel = "%s(%s)"%(value._yaxis, value._yunit)
[ef01be4]32        self.title(title=value.title)
[8cb6cd6]33
[9290b1a]34    def plot(self, data=None, marker=None, linestyle=None, hide_error=False):
[8cb6cd6]35        """
36        Plot self._data
37        """
[9290b1a]38        # Data1D
39        if isinstance(data, Data1D):
40            self.data = data
41        assert(self._data)
42
[6d05e1d]43        # Shortcut for an axis
[ef01be4]44        ax = self.ax
[8cb6cd6]45
[39551a68]46        if marker == None:
[6d05e1d]47            marker = 'o'
[39551a68]48
49        if linestyle == None:
[b4b8589]50            linestyle = ''
51
52        # plot data with/without errorbars
[c4e5400]53        if hide_error:
54            ax.plot(self._data.view.x, self._data.view.y,
55                    marker=marker,
56                    linestyle=linestyle,
[9290b1a]57                    label=self._title,
58                    picker=True)
[c4e5400]59        else:
60            ax.errorbar(self._data.view.x, self._data.view.y,
61                        yerr=self._data.view.dx, xerr=None,
62                        capsize=2, linestyle='',
63                        barsabove=False,
64                        marker=marker,
65                        lolims=False, uplims=False,
66                        xlolims=False, xuplims=False,
[9290b1a]67                        label=self._title,
68                        picker=True)
[8cb6cd6]69
70        # Now add the legend with some customizations.
[9290b1a]71        self.legend = ax.legend(loc='upper right', shadow=True)
72        #self.legend.get_frame().set_alpha(0.4)
73        self.legend.set_picker(True)
[8cb6cd6]74
[6d05e1d]75        # Current labels for axes
[ef01be4]76        ax.set_ylabel(self.y_label)
77        ax.set_xlabel(self.x_label)
[6d05e1d]78
[ef01be4]79        # Title only for regular charts
80        if not self.quickplot:
[6d05e1d]81            ax.set_title(label=self._title)
[8cb6cd6]82
[6d05e1d]83        # Include scaling (log vs. linear)
84        ax.set_xscale(self.xscale)
[3b7b218]85        ax.set_yscale(self.yscale)
[8cb6cd6]86
[257bd57]87        # define the ranges
88        self.setRange = SetGraphRange(parent=self,
89            x_range=self.ax.get_xlim(), y_range=self.ax.get_ylim())
90
[8cb6cd6]91        # refresh canvas
92        self.canvas.draw()
93
[c4e5400]94    def contextMenu(self):
95        """
96        Define common context menu and associated actions for the MPL widget
97        """
98        self.defaultContextMenu()
99
[27313b7]100        # Additional menu items
101        self.contextMenu.addSeparator()
102        self.actionModifyGraphAppearance =\
103            self.contextMenu.addAction("Modify Graph Appearance")
104        self.contextMenu.addSeparator()
105        self.actionAddText = self.contextMenu.addAction("Add Text")
106        self.actionRemoveText = self.contextMenu.addAction("Remove Text")
107        self.contextMenu.addSeparator()
108        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
109        self.contextMenu.addSeparator()
110        self.actionSetGraphRange = self.contextMenu.addAction("Set Graph Range")
111        self.actionResetGraphRange =\
112            self.contextMenu.addAction("Reset Graph Range")
113        # Add the title change for dialogs
114        if self.parent:
115            self.contextMenu.addSeparator()
116            self.actionWindowTitle = self.contextMenu.addAction("Window Title")
117
118        # Define the callbacks
119        self.actionModifyGraphAppearance.triggered.connect(self.onModifyGraph)
120        self.actionAddText.triggered.connect(self.onAddText)
121        self.actionRemoveText.triggered.connect(self.onRemoveText)
122        self.actionChangeScale.triggered.connect(self.onScaleChange)
123        self.actionSetGraphRange.triggered.connect(self.onSetGraphRange)
124        self.actionResetGraphRange.triggered.connect(self.onResetGraphRange)
125        self.actionWindowTitle.triggered.connect(self.onWindowsTitle)
[c4e5400]126
[6d05e1d]127    def contextMenuQuickPlot(self):
128        """
129        Define context menu and associated actions for the quickplot MPL widget
130        """
[c4e5400]131        # Default actions
132        self.defaultContextMenu()
133
134        # Additional actions
[6d05e1d]135        self.actionToggleGrid = self.contextMenu.addAction("Toggle Grid On/Off")
136        self.contextMenu.addSeparator()
137        self.actionChangeScale = self.contextMenu.addAction("Change Scale")
138
139        # Define the callbacks
140        self.actionToggleGrid.triggered.connect(self.onGridToggle)
141        self.actionChangeScale.triggered.connect(self.onScaleChange)
142
143    def onScaleChange(self):
144        """
145        Show a dialog allowing axes rescaling
146        """
147        if self.properties.exec_() == QtGui.QDialog.Accepted:
148            xLabel, yLabel = self.properties.getValues()
149            self.xyTransform(xLabel, yLabel)
150
[27313b7]151    def onModifyGraph(self):
152        """
153        Show a dialog allowing chart manipulations
154        """
155        print ("onModifyGraph")
156        pass
157
158    def onAddText(self):
159        """
160        Show a dialog allowing adding custom text to the chart
161        """
[9290b1a]162        if self.addText.exec_() == QtGui.QDialog.Accepted:
163            # Retrieve the new text, its font and color
164            extra_text = self.addText.text()
165            extra_font = self.addText.font()
166            extra_color = self.addText.color()
167
168            # Place the text on the screen at (0,0)
169            pos_x = self.x_click
170            pos_y = self.y_click
171
172            # Map QFont onto MPL font
173            mpl_font = FontProperties()
174            mpl_font.set_size(int(extra_font.pointSize()))
175            mpl_font.set_family(str(extra_font.family()))
176            mpl_font.set_weight(int(extra_font.weight()))
177            # MPL style names
178            styles = ['normal', 'italic', 'oblique']
179            # QFont::Style maps directly onto the above
180            try:
181                mpl_font.set_style(styles[extra_font.style()])
182            except:
183                pass
184
185            if len(extra_text) > 0:
186                new_text = self.ax.text(str(pos_x),
187                                        str(pos_y),
188                                        extra_text,
189                                        color=extra_color,
190                                        fontproperties=mpl_font)
191                # Update the list of annotations
192                self.textList.append(new_text)
193                self.canvas.draw_idle()
[27313b7]194
195    def onRemoveText(self):
196        """
[9290b1a]197        Remove the most recently added text
[27313b7]198        """
[d3ca363]199        num_text = len(self.textList)
200        if num_text < 1:
201            return
202        txt = self.textList[num_text - 1]
203        text_remove = txt.get_text()
204        txt.remove()
205        self.textList.remove(txt)
206
207        self.canvas.draw_idle()
[27313b7]208
209    def onSetGraphRange(self):
210        """
211        Show a dialog allowing setting the chart ranges
212        """
[d3ca363]213        # min and max of data
214        if self.setRange.exec_() == QtGui.QDialog.Accepted:
[257bd57]215            x_range = self.setRange.xrange()
216            y_range = self.setRange.yrange()
217            if x_range is not None and y_range is not None:
218                self.ax.set_xlim(x_range)
219                self.ax.set_ylim(y_range)
220                self.canvas.draw_idle()
[27313b7]221
222    def onResetGraphRange(self):
223        """
[d3ca363]224        Resets the chart X and Y ranges to their original values
[27313b7]225        """
[d3ca363]226        x_range = (self.data.x.min(), self.data.x.max())
227        y_range = (self.data.y.min(), self.data.y.max())
[257bd57]228        if x_range is not None and y_range is not None:
229            self.ax.set_xlim(x_range)
230            self.ax.set_ylim(y_range)
231            self.canvas.draw_idle()
[27313b7]232
[6d05e1d]233    def xyTransform(self, xLabel="", yLabel=""):
234        """
235        Transforms x and y in View and set the scale
236        """
237        # Clear the plot first
238        plt.cla()
[9290b1a]239        self.ax.cla()
[6d05e1d]240
241        # Changing the scale might be incompatible with
242        # currently displayed data (for instance, going
243        # from ln to log when all plotted values have
244        # negative natural logs).
245        # Go linear and only change the scale at the end.
246        self._xscale = "linear"
247        self._yscale = "linear"
248        _xscale = 'linear'
249        _yscale = 'linear'
250        # Local data is either 1D or 2D
251        if self.data.id == 'fit':
252            return
253
254        # control axis labels from the panel itself
255        yname, yunits = self.data.get_yaxis()
256        xname, xunits = self.data.get_xaxis()
257
258        # Goes through all possible scales
259        # self.x_label is already wrapped with Latex "$", so using the argument
260
261        # X
262        if xLabel == "x":
263            self.data.transformX(transform.toX, transform.errToX)
264            self.xLabel = "%s(%s)" % (xname, xunits)
265        if xLabel == "x^(2)":
266            self.data.transformX(transform.toX2, transform.errToX2)
267            xunits = convert_unit(2, xunits)
268            self.xLabel = "%s^{2}(%s)" % (xname, xunits)
269        if xLabel == "x^(4)":
270            self.data.transformX(transform.toX4, transform.errToX4)
271            xunits = convert_unit(4, xunits)
272            self.xLabel = "%s^{4}(%s)" % (xname, xunits)
273        if xLabel == "ln(x)":
274            self.data.transformX(transform.toLogX, transform.errToLogX)
275            self.xLabel = "\ln{(%s)}(%s)" % (xname, xunits)
276        if xLabel == "log10(x)":
277            self.data.transformX(transform.toX_pos, transform.errToX_pos)
278            _xscale = 'log'
279            self.xLabel = "%s(%s)" % (xname, xunits)
280        if xLabel == "log10(x^(4))":
281            self.data.transformX(transform.toX4, transform.errToX4)
282            xunits = convert_unit(4, xunits)
283            self.xLabel = "%s^{4}(%s)" % (xname, xunits)
284            _xscale = 'log'
285
286        # Y
287        if yLabel == "ln(y)":
288            self.data.transformY(transform.toLogX, transform.errToLogX)
289            self.yLabel = "\ln{(%s)}(%s)" % (yname, yunits)
290        if yLabel == "y":
291            self.data.transformY(transform.toX, transform.errToX)
292            self.yLabel = "%s(%s)" % (yname, yunits)
293        if yLabel == "log10(y)":
294            self.data.transformY(transform.toX_pos, transform.errToX_pos)
295            _yscale = 'log'
296            self.yLabel = "%s(%s)" % (yname, yunits)
297        if yLabel == "y^(2)":
298            self.data.transformY(transform.toX2, transform.errToX2)
299            yunits = convert_unit(2, yunits)
300            self.yLabel = "%s^{2}(%s)" % (yname, yunits)
301        if yLabel == "1/y":
302            self.data.transformY(transform.toOneOverX, transform.errOneOverX)
303            yunits = convert_unit(-1, yunits)
304            self.yLabel = "1/%s(%s)" % (yname, yunits)
305        if yLabel == "y*x^(2)":
306            self.data.transformY(transform.toYX2, transform.errToYX2)
307            xunits = convert_unit(2, xunits)
308            self.yLabel = "%s \ \ %s^{2}(%s%s)" % (yname, xname, yunits, xunits)
309        if yLabel == "y*x^(4)":
310            self.data.transformY(transform.toYX4, transform.errToYX4)
311            xunits = convert_unit(4, xunits)
312            self.yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
313        if yLabel == "1/sqrt(y)":
314            self.data.transformY(transform.toOneOverSqrtX,
315                                 transform.errOneOverSqrtX)
316            yunits = convert_unit(-0.5, yunits)
317            self.yLabel = "1/\sqrt{%s}(%s)" % (yname, yunits)
318        if yLabel == "ln(y*x)":
319            self.data.transformY(transform.toLogXY, transform.errToLogXY)
320            self.yLabel = "\ln{(%s \ \ %s)}(%s%s)" % (yname, xname, yunits, xunits)
321        if yLabel == "ln(y*x^(2))":
322            self.data.transformY(transform.toLogYX2, transform.errToLogYX2)
323            xunits = convert_unit(2, xunits)
324            self.yLabel = "\ln (%s \ \ %s^{2})(%s%s)" % (yname, xname, yunits, xunits)
325        if yLabel == "ln(y*x^(4))":
326            self.data.transformY(transform.toLogYX4, transform.errToLogYX4)
327            xunits = convert_unit(4, xunits)
328            self.yLabel = "\ln (%s \ \ %s^{4})(%s%s)" % (yname, xname, yunits, xunits)
329        if yLabel == "log10(y*x^(4))":
330            self.data.transformY(transform.toYX4, transform.errToYX4)
331            xunits = convert_unit(4, xunits)
332            _yscale = 'log'
333            self.yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
334
335        # Perform the transformation of data in data1d->View
336        self.data.transformView()
337
338        self.xscale = _xscale
339        self.yscale = _yscale
340
341        # Plot the updated chart
342        self.plot(marker='o', linestyle='')
[c4e5400]343
344
[416fa8f]345class Plotter(QtGui.QDialog, PlotterWidget):
346    def __init__(self, parent=None, quickplot=False):
347
348        QtGui.QDialog.__init__(self)
349        PlotterWidget.__init__(self, manager=parent, quickplot=quickplot)
[c4e5400]350        icon = QtGui.QIcon()
351        icon.addPixmap(QtGui.QPixmap(":/res/ball.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
352        self.setWindowIcon(icon)
353
[416fa8f]354
Note: See TracBrowser for help on using the repository browser.