3

I use pyqtgraph for data acquisition and I have to represent some thresholds on the graphics view. For example to represent a high voltage limit, etc. I used the class InfiniteLine from pyqtgraph, but now, I have to take into account some possible changes on the threshold value during the acquisition. It would look as a step between two infinite line (please find an example attached).

example

For this, I would have to draw a half infinite line. Do you know a simple way to do it?

I thought about using some plotCurveItem limited by the viewBox minimum and maximum :

thresholdValue = 60V # just an example
range = self.viewBox.viewRange()
xRange = range[0]   # we only want ViewBox horizontal limits
minView = xRange[0]
maxView = xRange[1]
myPlotCurveItem = pyqtgraph.PlotCurveItem([minView, maxView],[thresholdValue, thresholdValue])

In case of changing on threshold value :

newThresholdValue = 70V

the x data for the plotCurveItem would become :

[minView, changingTime]    #with changinTime : the moment we change the threshold

and we would add a new plotCurveItem :

myNewPlotCurveItem = pyqtgraph.plotCurveItem([changingTime, maxView],[newThresholdValue, newThresholdValue])

Does this solution looks good or do you see any problem with that?

NoDataDumpNoContribution
  • 10,591
  • 9
  • 64
  • 104
Momow
  • 143
  • 2
  • 8

2 Answers2

3

Your approach looks good and is mostly what pyqtgraph.InfiniteLine is doing. I examined the source of InfiniteLine and extracted those parts which are absolutely necessary and added the change point and two level information, then drawing three lines (left border to change point at left level, change point to right border at right level, connection of both).

Here is the full code:

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

class InfiniteLineWithBreak(pg.GraphicsObject):

    def __init__(self, changeX, levelsY, pen=None):
        pg.GraphicsObject.__init__(self)

        self.changeX = changeX
        self.levelsY = levelsY

        self.maxRange = [None, None]
        self.moving = False
        self.movable = False
        self.mouseHovering = False

        pen = (200, 200, 100)
        self.setPen(pen)
        self.setHoverPen(color=(255,0,0), width=self.pen.width())
        self.currentPen = self.pen


    def setBounds(self, bounds):
        self.maxRange = bounds
        self.setValue(self.value())

    def setPen(self, *args, **kwargs):
        self.pen = pg.fn.mkPen(*args, **kwargs)
        if not self.mouseHovering:
            self.currentPen = self.pen
            self.update()

    def setHoverPen(self, *args, **kwargs):
        self.hoverPen = pg.fn.mkPen(*args, **kwargs)
        if self.mouseHovering:
            self.currentPen = self.hoverPen
            self.update()

    def boundingRect(self):
        br = self.viewRect()
        return br.normalized()

    def paint(self, p, *args):
        br = self.boundingRect()
        p.setPen(self.currentPen)
        # three lines (left border to change point, change point vertical, change point to right)
        p.drawLine(pg.Point(br.left(), self.levelsY[0]), pg.Point(self.changeX, self.levelsY[0]))
        p.drawLine(pg.Point(self.changeX, self.levelsY[0]), pg.Point(self.changeX, self.levelsY[1]))
        p.drawLine(pg.Point(self.changeX, self.levelsY[1]), pg.Point(br.right(), self.levelsY[1]))

    def dataBounds(self, axis, frac=1.0, orthoRange=None):
        if axis == 0:
            return None   ## x axis should never be auto-scaled
        else:
            return (0,0)

    def setMouseHover(self, hover):
        pass

app = QtGui.QApplication([])
w = pg.GraphicsWindow()
w.resize(1000, 600)
v = w.addPlot(y=np.random.normal(size=100))
v.addItem(InfiniteLineWithBreak(changeX=50, levelsY=(-1, 1)))
app.exec_()

It looks like:

enter image description here

What one could add is reaction to hovering and changing the values with the mouse (change point as well as levels) or even rotate by 90 degree. InfiniteLine is a good example of how to do that.

NoDataDumpNoContribution
  • 10,591
  • 9
  • 64
  • 104
2

thanks you for you very complete answer ! Your code works very well. I made some modifications on your class InfiniteLineWithBreak in order to set multiple threshold transitions. I modified the init and the paint methods only:

def __init__(self, listOfcouplesOfThresholdAndItsDate, pen=None):
    pg.GraphicsObject.__init__(self)

    self.listOfcouplesOfThresholdAndItsDate=listOfcouplesOfThresholdAndItsDate
    self.maxRange = [None, None]
    self.moving = False
    self.movable = False
    self.mouseHovering = False

    pen = (200, 200, 100)
    self.setPen(pen)
    self.setHoverPen(color=(255,0,0), width=self.pen.width())
    self.currentPen = self.pen




 def paint(self, p, *args):
    br = self.boundingRect()
    p.setPen(self.currentPen)
    if len(self.listOfcouplesOfThresholdAndItsDate)==0:
        pass
    elif len(self.listOfcouplesOfThresholdAndItsDate)==1:
        threshold = self.listOfcouplesOfThresholdAndItsDate[0][1]
        date = self.listOfcouplesOfThresholdAndItsDate[0][0]
        p.drawLine(pg.Point(date, threshold), pg.Point(br.right(), threshold))

    else:

        threshold = self.listOfcouplesOfThresholdAndItsDate[0][1]
        date = self.listOfcouplesOfThresholdAndItsDate[0][0]
        i=0
        for i in range(0, len(self.listOfcouplesOfThresholdAndItsDate)-2):
            threshold = self.listOfcouplesOfThresholdAndItsDate[i][1]
            date = self.listOfcouplesOfThresholdAndItsDate[i][0]
            nexteDate = self.listOfcouplesOfThresholdAndItsDate[i+1][0]
            nextThreshold = self.listOfcouplesOfThresholdAndItsDate[i+1][1]
            p.drawLine(pg.Point(date, threshold), pg.Point(nexteDate, threshold))
            p.drawLine(pg.Point(nexteDate, threshold), pg.Point(nexteDate, nextThreshold))


        threshold = self.listOfcouplesOfThresholdAndItsDate[-2][1]
        date = self.listOfcouplesOfThresholdAndItsDate[-2][0]
        nexteDate = self.listOfcouplesOfThresholdAndItsDate[-1][0]
        nextThreshold = self.listOfcouplesOfThresholdAndItsDate[-1][1]
        p.drawLine(pg.Point(date, threshold), pg.Point(nexteDate, threshold))
        p.drawLine(pg.Point(nexteDate, threshold), pg.Point(nexteDate, nextThreshold))

        p.drawLine(pg.Point(nexteDate, nextThreshold), pg.Point(br.right(), nextThreshold))

Moreover, I added a method to append a new threshold transition point to the listOfcouplesOfThersholdAndItsDate :

def addANewThreshold(self,date, threshold):
    self.listOfcouplesOfThresholdAndItsDate.append((date, threshold))

Here is an example of what it looks like : example of multiple thresholds

Does the code look okay to you ? Thank you,

Momow
  • 143
  • 2
  • 8
  • Looks okay to me. Since you are new to Stackoverflow, do you know that you can also upvote answers additionally to marking them as solution? Also thanks for sharing your extended solution. – NoDataDumpNoContribution Jun 20 '16 at 19:51
  • Indeed, I tried to upvote your answer but it was not taken into account because I have not enough reputation points. Thank you again for your answer. – Momow Jun 21 '16 at 08:30