Package: dvg-pyqtgraph-threadsafe
DvG_PyQtGraph_ThreadSafePyQtGraph library providing thread-safe plot curves with underlying (ring)
buffers.Supports PyQt5, PyQt6, PySide2 and PySide6.Github:https://github.com/Dennis-van-Gils/python-dvg-pyqtgraph-threadsafePyPI:https://pypi.org/project/dvg-pyqtgraph-threadsafeInstallation:pip install dvg-pyqtgraph-threadsafeUseful linksDemos can be found in thedemos folder.Benchmark tests and results can be found in thebenchmark folder.Important notes on the specific use-case where another thread is actively requesting data at a high rate from an external device (e.g. an Arduino) to be pushed into a ThreadSafeCurve can be foundhere.OverviewClassesHistoryChartCurve,BufferedPlotCurve&PlotCurvewrap around
apyqtgraph.PlotDataIteminstance, called acurvefor convenience. Data
can be safely appended or set from out of any thread.The (x, y)-curve data is buffered internally to the class, relying on either a
circular/ring buffer or a regular array buffer:HistoryChartCurveRing buffer. The plotted x-data will be shifted such that the
right-side is always set to 0. I.e., whenxdenotes time, the data is
plotted backwards in time, hence the namehistorychart. The most
recent data is on the right-side of the ring buffer.BufferedPlotCurveRing buffer. Data will be plotted as is. Can also act as a Lissajous
figure.PlotCurveRegular array buffer. Data will be plotted as is.Usage:importsysfromPyQt5importQtWidgetsimportpyqtgraphaspgfromdvg_pyqtgraph_threadsafeimportHistoryChartCurveclassMainWindow(QtWidgets.QWidget):def__init__(self,parent=None,**kwargs):super().__init__(parent,**kwargs)self.gw=pg.GraphicsLayoutWidget()self.plot_1=self.gw.addPlot()# Create a HistoryChartCurve and have it wrap around a new PlotDataItem# as set by argument `linked_curve`.self.tscurve_1=HistoryChartCurve(capacity=5,linked_curve=self.plot_1.plot(pen=pg.mkPen('r')),)grid=QtWidgets.QGridLayout(self)grid.addWidget(self.gw)app=QtWidgets.QApplication(sys.argv)window=MainWindow()# The following line could have been executed from inside of another thread:window.tscurve_1.extendData([1,2,3,4,5],[10,20,30,20,10])# Draw the curve from out of the main threadwindow.tscurve_1.update()window.show()sys.exit(app.exec_())BenchmarkIf you intend to use this library to plot 100.000s of points at a high frame
rate on your integrated GPU instead of on a dedicated (performance) GPU,
you might run into performance issues. Even when OpenGL hardware
acceleration is enabled.I recommend you run the performance benchmark found in thebenchmark folder.
It can test for different PyQtGraph versions and Qt libraries on your
GPU of choice. I have found that the olderpyqtgraph 0.11library sometimes
results in a better performance thanpyqtgraph 0.12on integrated GPUs.
Seebenchmark results.APIclass ThreadSafeCurve(capacity: Optional[int], linked_curve: pg.PlotDataItem, shift_right_x_to_zero: bool = False, use_ringbuffer = None)Provides the base class for a thread-safe plotcurveto which
(x, y)-data can be safely appended or set from out of any thread. It
will wrap around the passed argumentlinked_curveof typepyqtgraph.PlotDataItemand will manage the (x, y)-data buffers
underlying the curve.Intended multi-threaded operation: One or more threads push new data
into theThreadSafeCurve-buffers. Another thread performs the GUI
refresh by callingupdate()which will redraw the curve according
to the current buffer contents.Args:capacity (int, optional):When an integer is supplied it defines the maximum number op points
each of the x-data and y-data buffers can store. The x-data buffer
and the y-data buffer are each a ring buffer. New readings are
placed at the end (right-side) of the buffer, pushing out the oldest
readings when the buffer has reached its maximum capacity (FIFO).
Use methodsappendData()andextendData()to push in new
data.WhenNoneis supplied the x-data and y-data buffers are each a
regular array buffer of undefined length. Use methodsetData()to set the data.linked_curve (pyqtgraph.PlotDataItem):Instance ofpyqtgraph.PlotDataItemto plot the buffered
data out into.shift_right_x_to_zero (bool, optional):When plotting, should the x-data be shifted such that the
right-side is always set to 0? Useful for history charts.Default: Falseuse_ringbuffer (bool, deprecated):Deprecated since v3.1.0. Defined for backwards compatibility.
Simply supply a value forcapacityto enable use of a ring
buffer.Attributes:x_axis_divisor (float):The x-data in the buffer will be divided by this factor when the
plot curve is drawn. Useful to, e.g., transform the x-axis units
from milliseconds to seconds or minutes.Default: 1y_axis_divisor (float):Same functionality asx_axis_divisor.Default: 1MethodsappendData(x, y)Append a single (x, y)-data point to the ring buffer.extendData(x_list, y_list)Extend the ring buffer with a list of (x, y)-data points.setData(x_list, y_list)Set the (x, y)-data of the regular array buffer.update(create_snapshot: bool = True)Update the data behind the curve by creating a snapshot of the
current contents of the buffer, and redraw the curve on screen.You can suppress updating the data behind the curve by setting parametercreate_snapshotto False. The curve will then only be redrawn
based on the old data. This is useful when the plot is paused.clear()Clear the contents of the curve and redraw.name()Get the name of the curve.isVisible()->boolsetVisible(state: bool = True)setDownsampling(*args,**kwargs)All arguments will be passed onto methodpyqtgraph.PlotDataItem.setDownsampling()of the underlying curve.Propertiessize->Tuple[int, int]:Number of elements currently contained in the underlying (x, y)-
buffers of the curve. Note that this is not necessarily the number of
elements of the currently drawn curve. Instead, it reflects the current
sizes of the data buffers behind it that will be drawn onto screen by
the next call toupdate().class HistoryChartCurve(capacity: int, linked_curve: pg.PlotDataItem)Bases:ThreadSafeCurveProvides a thread-safe curve with underlying ring buffers for the
(x, y)-data. New readings are placed at the end (right-side) of the
buffer, pushing out the oldest readings when the buffer has reached its
maximum capacity (FIFO). Use methodsappendData()andextendData()to push in new data.The plotted x-data will be shifted such that the right-side is always
set to 0. I.e., whenxdenotes time, the data is plotted backwards
in time, hence the namehistorychart.See classThreadSafeCurvefor more details.class BufferedPlotCurve(capacity: int, linked_curve: pg.PlotDataItem)Bases:ThreadSafeCurveProvides a thread-safe curve with underlying ring buffers for the
(x, y)-data. New readings are placed at the end (right-side) of the
buffer, pushing out the oldest readings when the buffer has reached its
maximum capacity (FIFO). Use methodsappendData()andextendData()to push in new data.See classThreadSafeCurvefor more details.class PlotCurve(capacity: int, linked_curve: pg.PlotDataItem)Bases:ThreadSafeCurveProvides a thread-safe curve with underlying regular array buffers
for the (x, y)-data. Use methodsetData()to set the data.See classThreadSafeCurvefor more details.class LegendSelect(curves: List[Union[pg.PlotDataItem,ThreadSafeCurve]],hide_toggle_button: bool = False, box_bg_color: QtGui.QColor = QtGui.QColor(0, 0, 0), box_width: int = 40, box_height: int = 23, parent=None)Bases:PyQt5.QtCore.QObjectCreates and manages a legend of all passed curves with checkboxes to
show or hide each curve. The legend ends with a push button to show or
hide all curves in one go. The full set of GUI elements is contained in
attributegridof typePyQt5.QtWidget.QGridLayoutto be added to
your GUI.The initial visibility, name and pen of each curve will be retrieved
from the members within the passed curves, i.e.:curve.isVisible()curve.name()curve.opts["pen"]Example grid:□ Curve 1  [  /  ]
□ Curve 2  [  /  ]
□ Curve 3  [  /  ]
[ Show / Hide all]Args:linked_curves (List[Union[pyqtgraph.PlotDataItem, ThreadSafeCurve]]):List ofpyqtgraph.PlotDataItemorThreadSafeCurveto be
controlled by the legend.hide_toggle_button (bool, optional):Default: Falsebox_bg_color (QtGui.QColor, optional):Background color of the legend boxes.Default:QtGui.QColor(0, 0, 0)box_width (int, optional):Default: 40box_height (int, optional):Default: 23Attributes:chkbs (List[PyQt5.QtWidgets.QCheckbox]):List of checkboxes to control the visiblity of each curve.painted_boxes (List[PyQt5.QtWidgets.QWidget]):List of painted boxes illustrating the pen of each curve.qpbt_toggle (PyQt5.QtWidgets.QPushButton):Push button instance that toggles showing/hiding all curves in
one go.grid (PyQt5.QtWidgets.QGridLayout):The full set of GUI elements combined into a grid to be added
to your GUI.Changelog3.3.0 (2023-02-27)RaiseImportErrorinstead of generalException3.2.6 (2022-10-13)Added link to notes on use-case: DAQ3.2.5 (2022-10-13)Minor edit: Using explicit argumentsxandyto set the curve data and set
the flagskipFiniteCheck=True. Both save (marginal) CPU time.3.2.4 (2022-10-12)Bug fix: Snapshot creation checks if internal buffer is of type RingBuffer and
casts to numpy.ndarray. This resolves an intermittent error where theisfiniteboolean return array was operating as mask on the RingBuffer, which
fails. The boolean return array now always operates on numpy.ndarray.Fix for external bug inpaintGLofpyqtgraphgraphicsItemsPlotCurveItem.py:
Added explicit check to ensure that the curve is only drawn when it has more
than 1 point.Added benchmark runningpython=3.10andpyqtgraph==0.13.13.2.3 (2022-10-11)Fixed bug when using PyQt6:QMessageBox.Yes–>QMessageBox.StandardButton.Yes3.2.2 (2022-09-18)Updated discussion on benchmarksThe batch file for auto-creation of the conda environments found in the
benchmark folder now uses the conda-forge channel.3.2.1 (2022-09-17)Neater mechanism to support both PyQt and PySideAdded benchmarksImproved demos3.2.0 (2022-09-13)Added support for PyQt5, PyQt6, PySide2 and PySide6 as suggested via a pull
request by Mathijs van Gorcum (https://github.com/mvgorcum).3.1.0 (2021-05-10)Removed redundant argumentcapacityfrom initPlotCurve(). It is
not using a ringbuffer and, hence, does not need a capacity.3.0.1 (2020-08-07)Bug-fixes:The use oftyping.TypedDictbroke support under Python 3.6
and 3.7. Fixed by conditional importtyping_extensions.Curve plotting was broken whensetClipToView(True)and the curve
data extended past the viewbox limits, when not using OpenGL. The cause was
my incorrect calculation ofconnect. Fixed by commenting outconnectagain. Curves will now show continuously (linear interpolation) whenever a NaN
is encountered, instead of as correctly fragmented. That’s acceptable.3.0.0 (2020-08-07)Renamed parameterLegendSelect.curvestoLegendSelect.linked_curvesChanged base of classLegendSelect()fromQWidgettoQObjectAdded classPlotManager()2.0.1 (2020-08-03)Workaround:PyQt5 >= 5.12.3causes a bug inPyQtGraphwhere a
curve won’t render if it contains NaNs (but only in the case when OpenGL is
disabled). The curve will now be displayed correctly, i.e., fragmented
whenever a NaN is encountered. When OpenGL is enabled, linear interpolation
will occur at the gaps as perpyqtgraph.plotCurveItem.paintGL().2.0.0 (2020-08-02)Method names are now conform thePyQtGraphnaming style. I.e.setData()vs.set_data(), etc.The default values ofPyQtGraphare no longer being overwritten.Added classLegendSelect1.0.0 (2020-07-30)First release on PyPI