Search code examples
pythonpyqtpyqt5qt-designerpyqtgraph

How to modify viewbox of existing PlotWidget, which is generated by QT designer?


I need to select and get data of two graphics from plots, I found a example, where it is created a custom viewbox and then create a plot like that pw = pg.PlotWidget(viewBox=vb), however I don't know how to do it when we have the plot as a Qt Creator promoted pyqtgraph widget. Below is the example that I found in a forum (link: https://groups.google.com/forum/#!topic/pyqtgraph/pTrem1RCKSw) Here is the code (works perfectly)

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Point import Point
from pyqtgraph.graphicsItems.ItemGroup import ItemGroup
from pyqtgraph.Qt import QtGui, QtCore
from matplotlib.mlab import inside_poly
from pyqtgraph.graphicsItems import ScatterPlotItem


class ChildGroup(ItemGroup):
    sigItemsChanged = QtCore.Signal()
    def __init__(self, parent):
        ItemGroup.__init__(self, parent)
        # excempt from telling view when transform changes
        self._GraphicsObject__inform_view_on_change = False
    def itemChange(self, change, value):
        ret = ItemGroup.itemChange(self, change, value)
        if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange:
            self.sigItemsChanged.emit()   
        return ret

class MyViewBox(pg.ViewBox):
      
    def mouseDragEvent(self, ev):
        
        if ev.button() == QtCore.Qt.RightButton:
            ev.ignore()    
        else:
            pg.ViewBox.mouseDragEvent(self, ev)
         
        ev.accept() 
        pos = ev.pos()
        if ev.button() == QtCore.Qt.RightButton:
            
            if ev.isFinish():  
                self.rbScaleBox.hide()
                self.ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos))
                self.ax = self.childGroup.mapRectFromParent(self.ax) 
                self.Coords =  self.ax.getCoords()  
                self.getdataInRect()
                self.changePointsColors()
            else:
                self.updateScaleBox(ev.buttonDownPos(), ev.pos())
           
    def getdataInRect(self):
        # Get the data from the Graphicsitem
        self.getDataItem()
        x = self.dataxy[0]
        y = self.dataxy[1]
        # Rect Edges
        Xbl = (self.Coords[0],self.Coords[1]) # bottom left
        Xbr = (self.Coords[2],self.Coords[1]) # bottom right
        Xtr = (self.Coords[2],self.Coords[3]) # top right
        Xtl = (self.Coords[0],self.Coords[3]) # top left
        #Make a list of [(x0,y0),(x1,y1) ...]
        self.xy = list()
        for i in x:
                tmp = (x[i],y[i])
                self.xy.append(tmp)            
        self.insideIndex = inside_poly(self.xy,[Xbl, Xbr, Xtr, Xtl])    
                 
    def getDataItem(self):
        
        self.ObjItemList = pg.GraphicsScene.items(self.scene(),self.ax)
        self.dataxy = self.ObjItemList[0].listDataItems()[0].getData()
       
    def changePointsColors(self):
        
        print(self.xy)
        print(self.insideIndex)
                  
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
mw.resize(800,500)
mw.show()

vb = MyViewBox()
pw = pg.PlotWidget(viewBox=vb) 

a  = np.array([0,1,2,3,4,5,6,7,8,9,10])
b  = np.array([0,1,2,3,4,5,6,7,8,9,10])

curve0 = pw.plot(a,b, clickable=True, symbol = '+')

mw.setCentralWidget(pw)

# Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

How could I adapt the previous code to the PlotWidget "Plot1"?

class MyApp(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
    pg.setConfigOption('background', 'w')
    QtWidgets.QMainWindow.__init__(self)
    Ui_MainWindow.__init__(self)
    self.setupUi(self)

    self.Plot1.plotItem.showGrid(True, True, 0.7)

    self.Plot1.setLabel('left', 'q1')
    self.Plot1.setLabel('bottom', 'Time', units='s')
    self.Plot1.setTitle(title='q1')

    pen = pg.mkPen({'color': "FF0000", 'width': 2})
    pen1 = pg.mkPen({'color': "00FF00", 'width': 2})
    self.Plot1.addLegend()
    self.q1z = self.Plot1.plot(pen = pen, name = 'Trace 1')
    self.q1k = self.Plot1.plot(pen = pen1, name = 'Trace 2')

Here the file Sample.ui

could someone help me?


Solution

  • The solution is to create a class that inherits from PlotWidget and has a viewbox to MyViewBox and then promote that widget:

    plotwidget.py

    import numpy as np
    
    import pyqtgraph as pg
    from pyqtgraph.Qt import QtGui, QtCore
    
    
    try:
        from matplotlib.mlab import inside_poly
    except ImportError:
        # https://matplotlib.org/3.1.0/api/api_changes.html
        # mlab.inside_poly has been removed in matplotlib 3.1.0
        from matplotlib.path import Path
    
        def inside_poly(points, verts):
            poly = Path(verts)
            return [idx for idx, p in enumerate(points) if poly.contains_point(p)]
    
    
    class ChildGroup(pg.ItemGroup):
        sigItemsChanged = QtCore.Signal()
    
        def __init__(self, parent):
            super(ChildGroup, self).__init__(parent)
            # excempt from telling view when transform changes
            self._GraphicsObject__inform_view_on_change = False
    
        def itemChange(self, change, value):
            ret = ItemGroup.itemChange(self, change, value)
            if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange:
                self.sigItemsChanged.emit()
            return ret
    
    
    class MyViewBox(pg.ViewBox):
        def mouseDragEvent(self, ev):
    
            if ev.button() == QtCore.Qt.RightButton:
                ev.ignore()
            else:
                pg.ViewBox.mouseDragEvent(self, ev)
    
            ev.accept()
            pos = ev.pos()
            if ev.button() == QtCore.Qt.RightButton:
                if ev.isFinish():
                    self.rbScaleBox.hide()
                    self.ax = QtCore.QRectF(
                        pg.Point(ev.buttonDownPos(ev.button())), pg.Point(pos)
                    )
                    self.ax = self.childGroup.mapRectFromParent(self.ax)
                    self.Coords = self.ax.getCoords()
                    self.getdataInRect()
                    self.changePointsColors()
                else:
                    self.updateScaleBox(ev.buttonDownPos(), ev.pos())
    
        def getdataInRect(self):
            # Get the data from the Graphicsitem
            self.getDataItem()
            x = self.dataxy[0]
            y = self.dataxy[1]
            # Rect Edges
            Xbl = (self.Coords[0], self.Coords[1])  # bottom left
            Xbr = (self.Coords[2], self.Coords[1])  # bottom right
            Xtr = (self.Coords[2], self.Coords[3])  # top right
            Xtl = (self.Coords[0], self.Coords[3])  # top left
            # Make a list of [(x0,y0),(x1,y1) ...]
            self.xy = list()
            for i in x:
                tmp = (x[i], y[i])
                self.xy.append(tmp)
            self.insideIndex = inside_poly(self.xy, [Xbl, Xbr, Xtr, Xtl])
    
        def getDataItem(self):
    
            self.ObjItemList = pg.GraphicsScene.items(self.scene(), self.ax)
            self.dataxy = self.ObjItemList[0].listDataItems()[0].getData()
    
        def changePointsColors(self):
            print(self.xy)
            print(self.insideIndex)
    
    
    class MyPlotWidget(pg.PlotWidget):
        def __init__(self, parent=None):
            super(MyPlotWidget, self).__init__(parent, viewBox=MyViewBox())
    

    Sample.ui

    <?xml version="1.0" encoding="UTF-8"?>
    <ui version="4.0">
     <class>Dialog</class>
     <widget class="QDialog" name="Dialog">
      <property name="geometry">
       <rect>
        <x>0</x>
        <y>0</y>
        <width>487</width>
        <height>368</height>
       </rect>
      </property>
      <property name="palette">
       <palette>
        <active>
         <colorrole role="Base">
          <brush brushstyle="SolidPattern">
           <color alpha="255">
            <red>255</red>
            <green>255</green>
            <blue>255</blue>
           </color>
          </brush>
         </colorrole>
         <colorrole role="Window">
          <brush brushstyle="SolidPattern">
           <color alpha="255">
            <red>86</red>
            <green>86</green>
            <blue>86</blue>
           </color>
          </brush>
         </colorrole>
        </active>
        <inactive>
         <colorrole role="Base">
          <brush brushstyle="SolidPattern">
           <color alpha="255">
            <red>255</red>
            <green>255</green>
            <blue>255</blue>
           </color>
          </brush>
         </colorrole>
         <colorrole role="Window">
          <brush brushstyle="SolidPattern">
           <color alpha="255">
            <red>86</red>
            <green>86</green>
            <blue>86</blue>
           </color>
          </brush>
         </colorrole>
        </inactive>
        <disabled>
         <colorrole role="Base">
          <brush brushstyle="SolidPattern">
           <color alpha="255">
            <red>86</red>
            <green>86</green>
            <blue>86</blue>
           </color>
          </brush>
         </colorrole>
         <colorrole role="Window">
          <brush brushstyle="SolidPattern">
           <color alpha="255">
            <red>86</red>
            <green>86</green>
            <blue>86</blue>
           </color>
          </brush>
         </colorrole>
        </disabled>
       </palette>
      </property>
      <property name="windowTitle">
       <string>Dialog</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout">
       <property name="leftMargin">
        <number>30</number>
       </property>
       <property name="topMargin">
        <number>40</number>
       </property>
       <property name="rightMargin">
        <number>40</number>
       </property>
       <property name="bottomMargin">
        <number>30</number>
       </property>
       <item>
        <widget class="MyPlotWidget" name="Plot1">
         <property name="autoFillBackground">
          <bool>false</bool>
         </property>
         <property name="backgroundBrush">
          <brush brushstyle="SolidPattern">
           <color alpha="255">
            <red>0</red>
            <green>0</green>
            <blue>0</blue>
           </color>
          </brush>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
     <customwidgets>
      <customwidget>
       <class>MyPlotWidget</class>
       <extends>QGraphicsView</extends>
       <header>plotwidget</header>
      </customwidget>
     </customwidgets>
     <resources/>
     <connections/>
    </ui>
    
    pyuic5 Sample.ui -o sample.py -x
    

    main.py

    from PyQt5 import QtWidgets
    
    import pyqtgraph as pg
    
    from sample import Ui_Dialog
    
    
    class MyApp(QtWidgets.QDialog, Ui_Dialog):
        def __init__(self, parent=None):
            super(MyApp, self).__init__(parent)
            pg.setConfigOption("background", "w")
            self.setupUi(self)
            self.Plot1.plotItem.showGrid(True, True, 0.7)
            self.Plot1.setLabel("left", "q1")
            self.Plot1.setLabel("bottom", "Time", units="s")
            self.Plot1.setTitle(title="q1")
    
            pen = pg.mkPen({"color": "FF0000", "width": 2})
            pen1 = pg.mkPen({"color": "00FF00", "width": 2})
            self.Plot1.addLegend()
            self.q1z = self.Plot1.plot(pen=pen, name="Trace 1")
            self.q1k = self.Plot1.plot(pen=pen1, name="Trace 2")
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MyApp()
        w.show()
        sys.exit(app.exec_())
    
    ├── main.py
    ├── plotwidget.py
    ├── sample.py
    └── Sample.ui