From 1dd6b9d26ea1e8dc74fb81182300deb06b2a84a0 Mon Sep 17 00:00:00 2001 From: Sietze van Buuren Date: Thu, 5 May 2022 20:29:51 +0200 Subject: [PATCH] Introduce API option to control whether lines are drawn as segmented lines (#2185) * Introduce API option to control whether lines are drawn as segmented lines - Adds a global options to set mode for drawing segmented lines - Mode can be 'auto' (use existing method), 'on' (always draw segmented lines), 'off' (never draw segmented lines) - Global option can be overridden for given ``PlotCurveItem`` instance using new setter method - **This is a proposal!** Added activation of anti-aliasing as criterion to decide whether to draw segmented lines in 'auto' mode * Added segmented line mode as option to `examples/PlotSpeedTest.py` Included 'segmentedLineMode' as argument to `setData` method in `PlotCurveItem.py` * Change of segmented line mode in `PlotSpeedTest.py` implemented analogue to pen options * Removed option to set segmentedLineMode through setData keyword Signed-off-by: Sietze van Buuren * Segmented line mode option now also accepts booleans `True` (same as `'on'`) and `False` (same as `'off'`) as possible modes * Removed `True`/`False` option possibilities for segmentedLineMode in `PlotCurveItem.py` Added documentation to `config_options.rst` for global option segmentedLineMode --- doc/source/config_options.rst | 4 +++ pyqtgraph/__init__.py | 8 +++++- pyqtgraph/examples/PlotSpeedTest.py | 7 ++++- pyqtgraph/graphicsItems/PlotCurveItem.py | 34 +++++++++++++++++++++++- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/doc/source/config_options.rst b/doc/source/config_options.rst index 767b26ff69..c14743ffc0 100644 --- a/doc/source/config_options.rst +++ b/doc/source/config_options.rst @@ -40,6 +40,10 @@ enableExperimental bool False Enable experimental fe * Only a very limited subset of the full options of PlotCurveItem is implemented. * Single precision is used. This may cause drawing artifacts. crashWarning bool False If True, print warnings about situations that may result in a crash. +segmentedLineMode str 'auto' For 'on', lines are always plotted in segments. For 'off', lines are never + plotted in segments. For 'auto', whether lines are plotted in segments is + automatically decided based on pen poperties and whether anti-aliasing is + enabled. ================== =================== ================== ================================================================================ diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py index 58a0f3506e..685cd54337 100644 --- a/pyqtgraph/__init__.py +++ b/pyqtgraph/__init__.py @@ -54,7 +54,11 @@ # change in the future. 'useCupy': False, # When True, attempt to use cupy ( currently only with ImageItem and related functions ) 'useNumba': False, # When True, use numba -} + 'segmentedLineMode': 'auto', # segmented line mode, controls if lines are plotted in segments or continuous + # 'auto': whether lines are plotted in segments is automatically decided using pen properties and whether anti-aliasing is enabled + # 'on' or True: lines are always plotted in segments + # 'off' or False: lines are never plotted in segments +} def setConfigOption(opt, value): @@ -62,6 +66,8 @@ def setConfigOption(opt, value): raise KeyError('Unknown configuration option "%s"' % opt) if opt == 'imageAxisOrder' and value not in ('row-major', 'col-major'): raise ValueError('imageAxisOrder must be either "row-major" or "col-major"') + if opt == 'segmentedLineMode' and value not in ('auto', 'on', 'off'): + raise ValueError('segmentedLineMode must be "auto", "on" or "off"') CONFIG_OPTIONS[opt] = value def setConfigOptions(**opts): diff --git a/pyqtgraph/examples/PlotSpeedTest.py b/pyqtgraph/examples/PlotSpeedTest.py index 10766743d6..97cee19527 100644 --- a/pyqtgraph/examples/PlotSpeedTest.py +++ b/pyqtgraph/examples/PlotSpeedTest.py @@ -80,7 +80,8 @@ def paint(self, painter, opt, widget): dict(name='connect', type='list', limits=['all', 'pairs', 'finite', 'array'], value='all'), dict(name='fill', type='bool', value=False), dict(name='skipFiniteCheck', type='bool', value=False), - dict(name='plotMethod', title='Plot Method', type='list', limits=['pyqtgraph', 'drawPolyline']) + dict(name='plotMethod', title='Plot Method', type='list', limits=['pyqtgraph', 'drawPolyline']), + dict(name='segmentedLineMode', title='Segmented lines', type='list', limits=['auto', 'on', 'off'], value='auto'), ] params = ptree.Parameter.create(name='Parameters', type='group', children=children) @@ -131,12 +132,16 @@ def onPenChanged(param, pen): def onFillChanged(param, enable): curve.setFillLevel(0.0 if enable else None) +def onSegmentedLineModeChanged(param, mode): + curve.setSegmentedLineMode(mode) + params.child('sigopts').sigTreeStateChanged.connect(makeData) params.child('useOpenGL').sigValueChanged.connect(onUseOpenGLChanged) params.child('enableExperimental').sigValueChanged.connect(onEnableExperimentalChanged) params.child('pen').sigValueChanged.connect(onPenChanged) params.child('fill').sigValueChanged.connect(onFillChanged) params.child('plotMethod').sigValueChanged.connect(curve.setMethod) +params.child('segmentedLineMode').sigValueChanged.connect(onSegmentedLineModeChanged) params.sigTreeStateChanged.connect(resetTimings) makeData() diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py index 2b4f5d8964..2b47b823c0 100644 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ b/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -151,7 +151,8 @@ def __init__(self, *args, **kargs): 'connect': 'all', 'mouseWidth': 8, # width of shape responding to mouse click 'compositionMode': None, - 'skipFiniteCheck': False + 'skipFiniteCheck': False, + 'segmentedLineMode': getConfigOption('segmentedLineMode'), } if 'pen' not in kargs: self.opts['pen'] = fn.mkPen('w') @@ -624,7 +625,35 @@ def getPath(self): return self.path + def setSegmentedLineMode(self, mode): + """ + Sets the mode that decides whether or not lines are drawn as segmented lines. Drawing lines + as segmented lines is more performant than the standard drawing method with continuous + lines. + + Parameters + ---------- + mode : str + ``'auto'`` (default) segmented lines are drawn if the pen's width > 1, pen style is a + solid line, the pen color is opaque and anti-aliasing is not enabled. + + ``'on'`` lines are always drawn as segmented lines + + ``'off'`` lines are never drawn as segmented lines, i.e. the drawing + method with continuous lines is used + """ + if mode not in ('auto', 'on', 'off'): + raise ValueError(f'segmentedLineMode must be "auto", "on" or "off", got {mode} instead') + self.opts['segmentedLineMode'] = mode + self.invalidateBounds() + self.update() + def _shouldUseDrawLineSegments(self, pen): + mode = self.opts['segmentedLineMode'] + if mode in ('on'): + return True + if mode in ('off'): + return False return ( pen.widthF() > 1.0 # non-solid pen styles need single polyline to be effective @@ -634,6 +663,9 @@ def _shouldUseDrawLineSegments(self, pen): and pen.isSolid() # pen.brush().style() == Qt.BrushStyle.SolidPattern # ends of adjacent line segments overlapping is visible when not opaque and pen.color().alphaF() == 1.0 + # anti-aliasing introduces transparent pixels and therefore also causes visible overlaps + # for adjacent line segments + and not self.opts['antialias'] ) def _getLineSegments(self):