【问题标题】:How can I make a PlotDataItem with unique IDs per distinct line segment?如何为每个不同的线段制作具有唯一 ID 的 PlotDataItem?
【发布时间】:2020-09-26 22:57:58
【问题描述】:

我也在pyqtgraph论坛发帖here

我的总体目标是在图像上覆盖多个可点击区域,如果点击任何区域的绘图边界,我会收到带有该区域 ID 的信号。像这样的东西:

如果我只使用一个带有 nan-separated 曲线的 PlotDataItem,那么每个边界都会发送相同的信号。但是,为每个边界使用单独的 PlotDataItem 会使应用程序非常缓慢。

我最终继承了 ScatterPlotItem 并重写了 pointsAt 函数,这正是我想要的。现在的问题是我想不出改变 ScatterPlotItem 的boundingRect 的适当方法。我的方法正确吗?有没有更好的方法来做到这一点?

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui


class CustScatter(pg.ScatterPlotItem):
  def pointsAt(self, pos: QtCore.QPointF):
    """
    The default implementation only checks a square around each spot. However, this is not
    precise enough for my needs. It also triggers when clicking *inside* the spot boundary,
    which I don't want.
    """
    pts = []
    for spot in self.points(): # type: pg.SpotItem
      symb = QtGui.QPainterPath(spot.symbol())
      symb.translate(spot.pos())
      stroker = QtGui.QPainterPathStroker()
      mousePath = stroker.createStroke(symb)
      # Only trigger when clicking a boundary, not the inside of the shape
      if mousePath.contains(pos):
        pts.append(spot)
    return pts[::-1]

"""Make some sample data"""
tri = np.array([[0,2.3,0,1,4,5,0], [0,4,4,8,8,3,0]]).T
tris = []
xyLocs = []
datas = []
for ii in np.arange(0, 16, 5):
  curTri = tri + ii
  tris.append(curTri)
  xyLocs.append(curTri.min(0))
  datas.append(ii)

def ptsClicked(item, pts):
  print(f'ID {pts[0].data()} Clicked!')

"""Logic for making spot shapes from a list of (x,y) vertices"""
def makeSymbol(verts: np.ndarray):
  outSymbol = QtGui.QPainterPath()
  symPath = pg.arrayToQPath(*verts.T)
  outSymbol.addPath(symPath)
  # From pyqtgraph.examples for plotting text
  br = outSymbol.boundingRect()
  tr = QtGui.QTransform()
  tr.translate(-br.x(), -br.y())
  outSymbol = tr.map(outSymbol)
  return outSymbol

app = pg.mkQApp()
pg.setConfigOption('background', 'w')

symbs = []
for xyLoc, tri in zip(xyLocs, tris):
  symbs.append(makeSymbol(tri))

"""Create the scatterplot"""
xyLocs = np.vstack(xyLocs)
tri2 = pg.PlotDataItem()
scat = CustScatter(*xyLocs.T, symbol=symbs, data=datas, connect='finite',
                   pxMode=False, brush=None, pen=pg.mkPen(width=5), size=1)
scat.sigClicked.connect(ptsClicked)
# Now each 'point' is one of the triangles, hopefully

"""Construct GUI window"""
w = pg.PlotWindow()
w.plotItem.addItem(scat)
plt: pg.PlotItem = w.plotItem
plt.showGrid(True, True, 1)
w.show()
app.exec()

【问题讨论】:

    标签: python qt5 pyqtgraph qgraphicsitem


    【解决方案1】:

    解决了!事实证明,除非您另外指定,否则数据集中每个符号的 boundingRect 都假定为 1,并且点大小是限制因素。在覆盖measureSpotSizes 之后,我的解决方案也有效:

    import numpy as np
    import pyqtgraph as pg
    from pyqtgraph.Qt import QtCore, QtGui
    
    
    class CustScatter(pg.ScatterPlotItem):
      def pointsAt(self, pos: QtCore.QPointF):
        """
        The default implementation only checks a square around each spot. However, this is not
        precise enough for my needs. It also triggers when clicking *inside* the spot boundary,
        which I don't want.
        """
        pts = []
        for spot in self.points(): # type: pg.SpotItem
          symb = QtGui.QPainterPath(spot.symbol())
          symb.translate(spot.pos())
          stroker = QtGui.QPainterPathStroker()
          mousePath = stroker.createStroke(symb)
          # Only trigger when clicking a boundary, not the inside of the shape
          if mousePath.contains(pos):
            pts.append(spot)
        return pts[::-1]
    
      def measureSpotSizes(self, dataSet):
        """
        Override the method so that it takes symbol size into account
        """
        for rec in dataSet:
          ## keep track of the maximum spot size and pixel size
          symbol, size, pen, brush = self.getSpotOpts(rec)
          br = symbol.boundingRect()
          size = max(br.width(), br.height())*2
          width = 0
          pxWidth = 0
          if self.opts['pxMode']:
            pxWidth = size + pen.widthF()
          else:
            width = size
            if pen.isCosmetic():
              pxWidth += pen.widthF()
            else:
              width += pen.widthF()
          self._maxSpotWidth = max(self._maxSpotWidth, width)
          self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
        self.bounds = [None, None]
    
    """Make some sample data"""
    tri = np.array([[0,2.3,0,1,4,5,0], [0,4,4,8,8,3,0]]).T
    tris = []
    xyLocs = []
    datas = []
    for ii in np.arange(0, 16, 5):
      curTri = tri + ii
      tris.append(curTri)
      xyLocs.append(curTri.min(0))
      datas.append(ii)
    
    def ptsClicked(item, pts):
      print(f'ID {pts[0].data()} Clicked!')
    
    """Logic for making spot shapes from a list of (x,y) vertices"""
    def makeSymbol(verts: np.ndarray):
      plotVerts = verts - verts.min(0, keepdims=True)
      symPath = pg.arrayToQPath(*plotVerts.T)
      return symPath
    
    app = pg.mkQApp()
    pg.setConfigOption('background', 'd')
    
    symbs = []
    for xyLoc, tri in zip(xyLocs, tris):
      symbs.append(makeSymbol(tri))
    
    """Create the scatterplot"""
    xyLocs = np.vstack(xyLocs)
    tri2 = pg.PlotDataItem()
    scat = CustScatter(*xyLocs.T, symbol=symbs, data=datas, connect='finite',
                       pxMode=False, brush=None, pen=pg.mkPen(width=5), size=1)
    scat.sigClicked.connect(ptsClicked)
    # Now each 'point' is one of the triangles, hopefully
    
    """Construct GUI window"""
    w = pg.PlotWindow()
    w.plotItem.addItem(scat)
    plt: pg.PlotItem = w.plotItem
    plt.showGrid(True, True, 1)
    w.show()
    app.exec()
    

    【讨论】:

      猜你喜欢
      • 2018-07-28
      • 1970-01-01
      • 1970-01-01
      • 2015-09-03
      • 1970-01-01
      • 2012-06-01
      • 2021-05-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多