Esempio n. 1
0
class PlotWidget(QWidget):
    """
    This class inherited from pyqtgraphs Plotwidget and MatplotlibWidget and adds additional compononets like:
        1) Cross-hair to view X, Y coordinates
        2) Changing plot-styles interactively
    """
    def __init__(self,parent=None,matplotlib=False):
        QWidget.__init__(self,parent)
        self.matplotlib=matplotlib
        self.mplPlotData={}
        self.mplErrorData={}
        self.xLabelFontSize=10
        self.yLabelFontSize=10
        self.titleFontSize=12
        self.xLabel='x'
        self.yLabel='y'
        self.title='Plot'            
        self.createPlotWidget()
        self.data={}
        self.dataErrPos={}
        self.dataErrNeg={}
        self.dataErr={}
        self.err={}
        self.data_num=0
        self.yerr={}
        self.fit={}
        
       
    def createPlotWidget(self):
        """
        Creates the plotWidget
        """
        self.vbLayout=QVBoxLayout(self)
        self.plotLayout=pg.LayoutWidget()
        self.vbLayout.addWidget(self.plotLayout)
        
        row=0
        col=0
        lineWidthLabel=QLabel('Line width')
        self.lineWidthLineEdit=QLineEdit('2')
        self.lineWidthLineEdit.returnPressed.connect(self.updatePlot)
        pointSizeLabel=QLabel('Point size')
        self.pointSizeLineEdit=QLineEdit('5')
        self.pointSizeLineEdit.returnPressed.connect(self.updatePlot)
        self.bgCheckBox=QCheckBox('White BG')
        self.bgCheckBox.stateChanged.connect(self.bgCheckBoxChanged)
        self.errorbarCheckBox=QCheckBox('Errorbar')
        self.errorbarCheckBox.stateChanged.connect(self.errorbarChanged)
        self.plotLayout.addWidget(lineWidthLabel,row=row,col=col)
        col+=1
        self.plotLayout.addWidget(self.lineWidthLineEdit,row=row,col=col)
        col+=1
        self.plotLayout.addWidget(pointSizeLabel,row=row,col=col)
        col+=1
        self.plotLayout.addWidget(self.pointSizeLineEdit,row=row,col=col)
        col+=1
        self.plotLayout.addWidget(self.bgCheckBox,row=row,col=col)
        col+=1
        self.plotLayout.addWidget(self.errorbarCheckBox,row=row,col=col)
        col=0
        row+=1
        if self.matplotlib:
            self.plotWidget=MatplotlibWidget()
            self.subplot=self.plotWidget.getFigure().add_subplot(111)
            self.plotWidget.fig.set_tight_layout(True)
            self.plotWidget.draw()
        else:
            self.plotWidget=pg.PlotWidget()
            self.plotWidget.getPlotItem().vb.scene().sigMouseMoved.connect(self.mouseMoved)
            self.legendItem=pg.LegendItem(offset=(0.0,1.0))
            self.legendItem.setParentItem(self.plotWidget.getPlotItem())
            
        self.plotLayout.addWidget(self.plotWidget,row=row,col=col,colspan=6)
        row+=1
        col=0 
        self.crosshairLabel=QLabel(u'X={: .5f} , y={: .5f}'.format(0.0,0.0))                                 
        self.xLogCheckBox=QCheckBox('LogX')
        self.xLogCheckBox.setTristate(False)
        self.xLogCheckBox.stateChanged.connect(self.updatePlot)
        self.yLogCheckBox=QCheckBox('LogY')
        self.yLogCheckBox.setTristate(False)
        self.yLogCheckBox.stateChanged.connect(self.updatePlot)
        if not self.matplotlib:
            self.plotLayout.addWidget(self.crosshairLabel,row=row,col=col,colspan=4)
        self.plotLayout.addWidget(self.xLogCheckBox,row=row,col=4)
        self.plotLayout.addWidget(self.yLogCheckBox,row=row,col=5)
        
        
        
    def bgCheckBoxChanged(self):
        if self.bgCheckBox.isChecked():
            self.plotWidget.setBackground('w')
            self.plotWidget.getAxis('left').setPen('k')
            self.plotWidget.getAxis('left').setTextPen('k')
            self.plotWidget.getAxis('bottom').setPen('k')
            self.plotWidget.getAxis('bottom').setTextPen('k')
        else:
            self.plotWidget.setBackground('k')
            self.plotWidget.getAxis('left').setPen('w')
            self.plotWidget.getAxis('left').setTextPen('w')
            self.plotWidget.getAxis('bottom').setPen('w')
            self.plotWidget.getAxis('bottom').setTextPen('w')


    def mouseMoved(self,pos):
        try:
            pointer=self.plotWidget.getPlotItem().vb.mapSceneToView(pos)
            x,y=pointer.x(),pointer.y()
            if self.plotWidget.getPlotItem().ctrl.logXCheck.isChecked():
                x=10**x
            if self.plotWidget.getPlotItem().ctrl.logYCheck.isChecked():
                y=10**y
            self.crosshairLabel.setText('X={: 10.5f}, Y={: 10.5e}'.format(x,y))
            # if x>1e-3 and y>1e-3:
            #     self.crosshairLabel.setText(u'X={: .5f} , Y={: .5f}'.format(x,y))
            # if x<1e-3 and y>1e-3:
            #     self.crosshairLabel.setText(u'X={: .3e} , Y={: .5f}'.format(x,y))
            # if x>1e-3 and y<1e-3:
            #     self.crosshairLabel.setText(u'X={: .5f} , Y={: .3e}'.format(x,y))
            # if x<1e-3 and y<1e-3:
            #     self.crosshairLabel.setText(u'X={: .3e} , Y={: .3e}'.format(x,y))
        except:
            pass
                
        #self.crosshairLabel.setText(u'X=%+0.5f, Y=%+0.5e'%(x,y))
        
        
    def add_data(self,x,y,yerr=None,name=None,fit=False,color=None):
        """
        Adds data into the plot where:
        x=Array of x-values
        y=Array of y-values
        yerr=Array of yerr-values. If None yerr will be set to sqrt(y)
        name=any string to be used for the key to put the data
        fit= True if the data corresponds to a fit
        """
        if not (isinstance(yerr,list) or isinstance(yerr,np.ndarray)):
            yerr=np.ones_like(y)
        x=np.array(x)
        y=np.array(y)
        if len(x)==len(y) and len(y)==len(yerr):
            if name is None:
                dname=str(self.data_num)
            else:
                dname=name
            if np.all(yerr==1):
                self.yerr[dname]=False
            else:
                self.yerr[dname]=True
            self.fit[dname]=fit
            if dname in self.data.keys():
                if color is None:
                    color=self.data[dname].opts['symbolPen'].color()
                pen=pg.mkPen(color=color,width=float(self.lineWidthLineEdit.text()))
                symbol='o'
                if self.fit[dname]:
                    symbol=None
                self.data[dname].setData(x,y,pen=pen,symbol=symbol,symbolSize=float(self.pointSizeLineEdit.text()),symbolPen=pg.mkPen(color=color),symbolBrush=pg.mkBrush(color=color))
                #self.data[dname].setPen(pg.mkPen(color=pg.intColor(np.random.choice(range(0,210),1)[0]),width=int(self.lineWidthLineEdit.text())))
                #if self.errorbarCheckBox.isChecked():
                # self.dataErrPos[dname].setData(x,np.where(y+yerr/2.0>0,y+yerr/2.0,y))
                # self.dataErrNeg[dname].setData(x,np.where(y-yerr/2.0>0,y-yerr/2.0,y))
                self.err[dname]= yerr
                self.dataErr[dname].setData(x=x, y=y, top=self.err[dname], bottom=self.err[dname], pen='w')# beam=min(np.abs(x))*0.01*float(self.pointSizeLineEdit.text()),pen='w')
            #self.dataErr[dname].setCurves(self.dataErrPos[dname],self.dataErrNeg[dname])
            else:
                if color is None:
                    color=pg.intColor(np.random.choice(range(0,210),1)[0])
                #color=self.data[dname].opts['pen'].color()
                pen=pg.mkPen(color=color,width=float(self.lineWidthLineEdit.text()))
                symbol='o'
                if self.fit[dname]:
                    symbol=None
                self.data[dname]=pg.PlotDataItem(x,y,pen=pen,symbol=symbol,symbolSize=float(self.pointSizeLineEdit.text()),symbolPen=pg.mkPen(color=color),symbolBrush=pg.mkBrush(color=color))
                self.dataErr[dname] = pg.ErrorBarItem()
                self.err[dname]=yerr
                self.dataErr[dname].setData(x=x,y=y,top=self.err[dname],bottom=self.err[dname], pen='w')# beam=min(np.abs(x))*0.01*float(self.pointSizeLineEdit.text()),pen='w')
                self.data[dname].curve.setClickable(True,width=10)
                self.data[dname].sigClicked.connect(self.colorChanged)
                #if self.errorbarCheckBox.isChecked():
                # self.dataErrPos[dname]=pg.PlotDataItem(x,np.where(y+yerr/2.0>0,y+yerr/2.0,y))
                # self.dataErrNeg[dname]=pg.PlotDataItem(x,np.where(y-yerr/2.0>0,y-yerr/2.0,y))
                #self.dataErr[dname]=pg.FillBetweenItem(curve1=self.dataErrPos[dname],curve2=self.dataErrNeg[dname],brush=pg.mkBrush(color=pg.hsvColor(1.0,sat=0.0,alpha=0.2)))
                self.data_num+=1
                #if len(x)>1:
                self.Plot([dname])
            return True
        else:
            QMessageBox.warning(self,'Data error','The dimensions of x, y or yerr are not matching',QMessageBox.Ok)
            return False
            
    def colorChanged(self,item):
        """
        Color of the item changed
        """
        color=item.opts['symbolPen'].color()
        newcolor=QColorDialog.getColor(initial=color)
        if newcolor.isValid():
            #if self.lineWidthLineEdit.text()!='0':
            item.setPen(pg.mkPen(color=newcolor,width=int(self.lineWidthLineEdit.text())))
            if self.pointSizeLineEdit.text()!='0':
                item.setSymbolBrush(pg.mkBrush(color=newcolor))
                item.setSymbolPen(pg.mkPen(color=newcolor))
    
    def errorbarChanged(self):
        """
        Updates the plot checking the Errorbar is checked or not
        """
        try:
            self.Plot(self.selDataNames)
        except:
            pass
            
        
    def Plot(self,datanames):
        """
        Plots all the data in the memory with errorbars where:
            datanames is the list of datanames
        """
        self.selDataNames=datanames
        if self.matplotlib: #Plotting with matplotlib
            names=list(self.mplPlotData.keys())
            for name in names:
                if name not in self.selDataNames:
                    self.mplPlotData[name].remove()
                    del self.mplPlotData[name]
                    try:
                        self.mplErrorData[name].remove()
                        del self.mplErrorData[name]
                    except:
                        pass
            self.xLabel=self.subplot.get_xlabel()
            self.yLabel=self.subplot.get_ylabel()
            self.title=self.subplot.get_title()
            #self.subplot.axes.cla()
            for dname in self.selDataNames:
                if self.fit[dname]:
                    plot_type = '-'
                else:
                    plot_type = '.-'
                if self.errorbarCheckBox.checkState()==Qt.Checked:
                    try:
                        self.mplPlotData[dname].set_xdata(self.data[dname].xData)
                        self.mplPlotData[dname].set_ydata(self.data[dname].yData)
                        self.mplPlotData[dname].set_markersize(int(self.pointSizeLineEdit.text()))
                        self.mplPlotData[dname].set_linewidth(int(self.lineWidthLineEdit.text()))

                        self.mplErrorData[dname].set_segments(np.array([[x,yt],[x,yb]]) for x,yt,yb in zip(self.data[dname].xData,self.dataErrPos[dname].yData,self.dataErrNeg[dname].yData))
                        self.mplErrorData[dname].set_linewidth(2)
                    except:
                        ln,err,bar =self.subplot.errorbar(self.data[dname].xData,self.data[dname].yData,xerr=None,yerr=self.dataErr[dname].opts['top']*2,fmt=plot_type,markersize=int(self.pointSizeLineEdit.text()),linewidth=int(self.lineWidthLineEdit.text()),label=dname)
                        self.mplPlotData[dname]=ln
                        self.mplErrorData[dname],=bar
                else:
                    try:
                        self.mplPlotData[dname].set_xdata(self.data[dname].xData)
                        self.mplPlotData[dname].set_ydata(self.data[dname].yData)
                        self.mplPlotData[dname].set_markersize(int(self.pointSizeLineEdit.text()))
                        self.mplPlotData[dname].set_linewidth(int(self.lineWidthLineEdit.text()))
                    except:
                        self.mplPlotData[dname], =self.subplot.plot(self.data[dname].xData,self.data[dname].yData,plot_type,markersize=int(self.pointSizeLineEdit.text()),linewidth=int(self.lineWidthLineEdit.text()),label=dname)
            if self.xLogCheckBox.checkState()==Qt.Checked:
                self.subplot.set_xscale('log')
            else:
                self.subplot.set_xscale('linear')
            if self.yLogCheckBox.checkState()==Qt.Checked:
                self.subplot.set_yscale('log')
            else:
                self.subplot.set_yscale('linear')
            self.subplot.set_xlabel(self.xLabel,fontsize=self.xLabelFontSize)
            self.subplot.set_ylabel(self.yLabel,fontsize=self.yLabelFontSize)
            self.subplot.set_title(self.title,fontsize=self.titleFontSize)
#            try:
#                self.leg.draggable()
#            except:
            self.leg=self.subplot.legend()
            self.leg.set_draggable(True)
            self.plotWidget.fig.set_tight_layout(True)
            self.plotWidget.draw()
                
        else:
            self.plotWidget.plotItem.setLogMode(x=False,y=False)
            self.plotWidget.clear()
            for names in self.data.keys():
                self.legendItem.removeItem(names)
            xlog_res=True
            ylog_res=True
            for dname in self.selDataNames:
                if np.all(self.data[dname].yData==0) and self.yLogCheckBox.checkState()==Qt.Checked: #This step is necessary for checking the zero values
                    QMessageBox.warning(self,'Zero error','All the yData are zeros. So Cannot plot Logarithm of yData for %s'%dname,QMessageBox.Ok)
                    ylog_res=ylog_res and False
                    if not ylog_res:
                        self.yLogCheckBox.stateChanged.disconnect(self.updatePlot)
                        self.yLogCheckBox.setCheckState(Qt.Unchecked)
                        self.yLogCheckBox.stateChanged.connect(self.updatePlot)
                if np.all(self.data[dname].xData==0) and self.xLogCheckBox.checkState()==Qt.Checked:
                    QMessageBox.warning(self,'Zero error','All the xData are zeros. So Cannot plot Logarithm of xData for %s'%dname,QMessageBox.Ok)
                    xlog_res=xlog_res and False
                    if not xlog_res:
                        self.xLogCheckBox.stateChanged.disconnect(self.updatePlot)
                        self.xLogCheckBox.setCheckState(Qt.Unchecked)
                        self.xLogCheckBox.stateChanged.connect(self.updatePlot)
                self.plotWidget.addItem(self.data[dname])
                if self.errorbarCheckBox.isChecked() and self.yerr[dname]:
                    x=self.data[dname].xData
                    y=self.data[dname].yData
                    top=copy.copy(self.err[dname])
                    bottom= copy.copy(self.err[dname])
                    if self.xLogCheckBox.checkState() == Qt.Checked:
                         x=np.log10(x)
                    if self.yLogCheckBox.checkState() == Qt.Checked:
                        top=np.log10(1+top/y)
                        bottom=np.log10(y/(y-bottom))
                        y = np.log10(y)
                    self.dataErr[dname].setData(x=x,y=y,top=top,bottom=bottom,pen='w')#beam=min(np.abs(x))*0.01*float(self.pointSizeLineEdit.text()),pen='w')
                    self.plotWidget.addItem(self.dataErr[dname])
                    # self.plotWidget.addItem(self.dataErrPos[dname])
                    # self.plotWidget.addItem(self.dataErrNeg[dname])
                    #self.plotWidget.addItem(self.dataErr[dname])
                    #self.dataErr[dname].setCurves(self.dataErrPos[dname],self.dataErrNeg[dname])
                self.legendItem.addItem(self.data[dname],dname)
            if self.xLogCheckBox.checkState()==Qt.Checked:
                self.plotWidget.plotItem.setLogMode(x=True)
            else:
                self.plotWidget.plotItem.setLogMode(x=False)
            if self.yLogCheckBox.checkState()==Qt.Checked:
                self.plotWidget.plotItem.setLogMode(y=True)
            else:
                self.plotWidget.plotItem.setLogMode(y=False)

                
    def remove_data(self,datanames):
        for dname in datanames:
            if self.matplotlib:
                self.mplPlotData[dname].remove()
                del self.mplPlotData[dname]
                if self.errorbarCheckBox.isChecked() and self.yerr[dname]:
                    self.mplErrorData[dname].remove()
                    del self.mplErrorData[dname]
            else:
                self.plotWidget.removeItem(self.data[dname])
                self.legendItem.removeItem(dname)
                if self.errorbarCheckBox.isChecked() and self.yerr[dname]:
                    self.plotWidget.removeItem(self.dataErr[dname])
                    # self.plotWidget.removeItem(self.dataErrPos[dname])
                    # self.plotWidget.removeItem(self.dataErrNeg[dname])
            del self.data[dname]
            del self.dataErr[dname]
            # del self.dataErrPos[dname]
            # del self.dataErrNeg[dname]
            
            
    def updatePlot(self):
        try:
            for dname in self.selDataNames:
                if self.lineWidthLineEdit.text()=='0' and not self.fit[dname]:
                    self.data[dname].opts['pen']=None
                    self.data[dname].updateItems()
                else:
                    #try:
                    self.data[dname].setPen(self.data[dname].opts['symbolPen']) #setting the same color as the symbol
                    color=self.data[dname].opts['pen'].color()
                    self.data[dname].setPen(pg.mkPen(color=color,width=float(self.lineWidthLineEdit.text())))
                    #except:
                    #    self.data[dname].setPen(pg.mkPen(color='b',width=float(self.lineWidthLineEdit.text())))
                self.data[dname].opts['symbol']='o'
                if self.fit[dname]:
                    self.data[dname].setSymbolSize(0)
                else:
                    self.data[dname].setSymbolSize(float(self.pointSizeLineEdit.text()))
            self.Plot(self.selDataNames)
        except:
            QMessageBox.warning(self,'Data Error','No data to plot',QMessageBox.Ok)
            
    def setXLabel(self,label,fontsize=4):
        """
        sets the X-label of the plot
        """
        self.xLabel=label
        self.xLabelFontSize=fontsize
        if self.matplotlib:
            self.subplot.set_xlabel(label,fontsize=fontsize)
            self.plotWidget.draw()
        else:
            self.plotWidget.getPlotItem().setLabel('bottom','<font size='+str(fontsize)+'>'+label+'</font>')
            
    def setYLabel(self,label,fontsize=4):
        """
        sets the y-label of the plot
        """
        self.yLabel=label
        self.yLabelFontSize=fontsize
        if self.matplotlib:
            self.subplot.set_ylabel(label,fontsize=fontsize)
            self.plotWidget.draw()
        else:
            self.plotWidget.getPlotItem().setLabel('left','<font size='+str(fontsize)+'>'+label+'</font>')        
            
            
    def setTitle(self,title,fontsize=6):
        """
        Sets the y-label of the plot
        """
        self.title=title
        self.titleFontSize=fontsize
        if self.matplotlib:
            self.subplot.set_title(title,fontsize=fontsize)
            self.plotWidget.draw()
        else:
            self.plotWidget.getPlotItem().setTitle(title='<font size='+str(fontsize)+'>'+title+'</font>')

    def addROI(self,values=(0,1),orientation='horizontal',movable=True, minmax_widget=None, min_widget=None, max_widget=None):
        if orientation=='vertical':
            self.roi=pg.LinearRegionItem(values=values,orientation=pg.LinearRegionItem.Vertical,movable=movable)
        else:
            self.roi = pg.LinearRegionItem(values=values, orientation=pg.LinearRegionItem.Horizontal,movable=movable)
        self.plotWidget.addItem(self.roi)
        return self.roi