Repository: pyqtgraph/pyqtgraph

Issue:
commit 574c2d2 breaks a lot of PlotDataItem methods
Commit 574c2d2 / pull request #1619 broke a lot of my code. I have the user being allowed to update the line styles, pens, etc., and all that appeared to stop working.

After looking through it, it appears that this is because PlotDataItem.updateItems() was given the default argument update_style=False. This method is called in many places in PlotDataItem without the update_style argument. For example, setPen(), setSymbol(), etc. In all of these locations, when updateItems() is called, it should be called with update_style=True.

I'm afraid I'm not comfortable enough with the codebase to put in a pull request to fix this, as there are many places in PlotDataItem that updateItems() is called where I'm unsure what the update_style argument should be. It might also be worth seeing where this fell through the cracks in the testing framework.

A contrived minimal working example is below (it was taken from https://www.learnpyqt.com/tutorials/plotting-pyqtgraph/, with 1 line added). Running it under commit 5fa3901 (the prior commit) shows the line widths being updated a few times a second. Running it under commit 574c2d2 shows this not happening.

```
from PySide2 import QtWidgets, QtCore
from pyqtgraph import PlotWidget, plot
import pyqtgraph as pg
import sys  # We need sys so that we can pass argv to QApplication
import os
from random import randint

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        self.x = list(range(100))  # 100 time points
        self.y = [randint(0,100) for _ in range(100)]  # 100 data points

        self.graphWidget.setBackground('w')

        pen = pg.mkPen(color=(255, 0, 0))
        self.data_line =  self.graphWidget.plot(self.x, self.y, pen=pen)

        self.timer = QtCore.QTimer()
        self.timer.setInterval(50)
        self.timer.timeout.connect(self.update_plot_data)
        self.timer.start()

    def update_plot_data(self):

        self.x = self.x[1:]  # Remove the first y element.
        self.x.append(self.x[-1] + 1)  # Add a new value 1 higher than the last.

        self.y = self.y[1:]  # Remove the first 
        self.y.append( randint(0,100))  # Add a new random value.

        self.data_line.setData(self.x, self.y)  # Update the data.

        self.data_line.setPen(width=randint(1,10))  # Update the line widths (added to show issue)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
```


Discussion:
@NilsNemitz @j9ac9k figure you guys want to know.
Thanks for bringing this to our attention.  I'll try and look into it shortly.

Resolving patch:
diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py
--- a/pyqtgraph/graphicsItems/PlotDataItem.py
+++ b/pyqtgraph/graphicsItems/PlotDataItem.py
@@ -242,7 +242,7 @@ def setFftMode(self, mode):
             return
         self.opts['fftMode'] = mode
         self.xDisp = self.yDisp = None
-        self.updateItems()
+        self.updateItems(styleUpdate=False)
         self.informViewBoundsChanged()
 
     def setLogMode(self, xMode, yMode):
@@ -260,7 +260,7 @@ def setLogMode(self, xMode, yMode):
             return
         self.opts['logMode'] = [xMode, yMode]
         self.xDisp = self.yDisp = None
-        self.updateItems()
+        self.updateItems(styleUpdate=False)
         self.informViewBoundsChanged()
 
 
@@ -269,7 +269,7 @@ def setDerivativeMode(self, mode):
             return
         self.opts['derivativeMode'] = mode
         self.xDisp = self.yDisp = None
-        self.updateItems()
+        self.updateItems(styleUpdate=False)
         self.informViewBoundsChanged()
 
     def setPhasemapMode(self, mode):
@@ -277,7 +277,7 @@ def setPhasemapMode(self, mode):
             return
         self.opts['phasemapMode'] = mode
         self.xDisp = self.yDisp = None
-        self.updateItems()
+        self.updateItems(styleUpdate=False)
         self.informViewBoundsChanged()
 
     def setPointMode(self, mode):
@@ -297,7 +297,7 @@ def setPen(self, *args, **kargs):
         #for c in self.curves:
             #c.setPen(pen)
         #self.update()
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
     def setShadowPen(self, *args, **kargs):
         """
@@ -312,14 +312,14 @@ def setShadowPen(self, *args, **kargs):
         #for c in self.curves:
             #c.setPen(pen)
         #self.update()
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
     def setFillBrush(self, *args, **kargs):
         brush = fn.mkBrush(*args, **kargs)
         if self.opts['fillBrush'] == brush:
             return
         self.opts['fillBrush'] = brush
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
     def setBrush(self, *args, **kargs):
         return self.setFillBrush(*args, **kargs)
@@ -328,14 +328,14 @@ def setFillLevel(self, level):
         if self.opts['fillLevel'] == level:
             return
         self.opts['fillLevel'] = level
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
     def setSymbol(self, symbol):
         if self.opts['symbol'] == symbol:
             return
         self.opts['symbol'] = symbol
         #self.scatter.setSymbol(symbol)
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
     def setSymbolPen(self, *args, **kargs):
         pen = fn.mkPen(*args, **kargs)
@@ -343,7 +343,7 @@ def setSymbolPen(self, *args, **kargs):
             return
         self.opts['symbolPen'] = pen
         #self.scatter.setSymbolPen(pen)
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
     def setSymbolBrush(self, *args, **kargs):
         brush = fn.mkBrush(*args, **kargs)
@@ -351,7 +351,7 @@ def setSymbolBrush(self, *args, **kargs):
             return
         self.opts['symbolBrush'] = brush
         #self.scatter.setSymbolBrush(brush)
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
 
     def setSymbolSize(self, size):
@@ -359,7 +359,7 @@ def setSymbolSize(self, size):
             return
         self.opts['symbolSize'] = size
         #self.scatter.setSymbolSize(symbolSize)
-        self.updateItems()
+        self.updateItems(styleUpdate=True)
 
     def setDownsampling(self, ds=None, auto=None, method=None):
         """
@@ -396,14 +396,14 @@ def setDownsampling(self, ds=None, auto=None, method=None):
 
         if changed:
             self.xDisp = self.yDisp = None
-            self.updateItems()
+            self.updateItems(styleUpdate=False)
 
     def setClipToView(self, clip):
         if self.opts['clipToView'] == clip:
             return
         self.opts['clipToView'] = clip
         self.xDisp = self.yDisp = None
-        self.updateItems()
+        self.updateItems(styleUpdate=False)
 
     def setDynamicRangeLimit(self, limit=1e06, hysteresis=3.):
         """
@@ -431,7 +431,7 @@ def setDynamicRangeLimit(self, limit=1e06, hysteresis=3.):
             return # avoid update if there is no change
         self.opts['dynamicRangeLimit'] = limit # can be None
         self.xDisp = self.yDisp = None
-        self.updateItems()
+        self.updateItems(styleUpdate=False)
 
     def setData(self, *args, **kargs):
         """
@@ -575,7 +575,7 @@ def setData(self, *args, **kargs):
         self.yDisp = None
         profiler('set data')
 
-        self.updateItems( update_style = self._styleWasChanged )
+        self.updateItems( styleUpdate = self._styleWasChanged )
         self._styleWasChanged = False # items have been updated
         profiler('update items')
 
@@ -587,10 +587,10 @@ def setData(self, *args, **kargs):
         self.sigPlotChanged.emit(self)
         profiler('emit')
 
-    def updateItems(self, update_style=False):
+    def updateItems(self, styleUpdate=True):
         curveArgs = {}
         scatterArgs = {}
-        if update_style: # repeat style arguments only when changed
+        if styleUpdate: # repeat style arguments only when changed
             for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillOutline', 'fillOutline'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect'), ('stepMode', 'stepMode')]:
                 if k in self.opts:
                     curveArgs[v] = self.opts[k]
@@ -872,10 +872,10 @@ def viewRangeChanged(self):
             or self.opts['autoDownsample']
         ):
             self.xDisp = self.yDisp = None
-            self.updateItems()
+            self.updateItems(styleUpdate=False)
         elif self.opts['dynamicRangeLimit'] is not None:
-            # do not discard cached display data
-            self.updateItems()
+            # update, but do not discard cached display data
+            self.updateItems(styleUpdate=False)
 
     def _fourierTransform(self, x, y):
         ## Perform fourier transform. If x values are not sampled uniformly,


Test patch:
diff --git a/pyqtgraph/graphicsItems/tests/test_PlotDataItem.py b/pyqtgraph/graphicsItems/tests/test_PlotDataItem.py
--- a/pyqtgraph/graphicsItems/tests/test_PlotDataItem.py
+++ b/pyqtgraph/graphicsItems/tests/test_PlotDataItem.py
@@ -1,5 +1,6 @@
 import numpy as np
 import pyqtgraph as pg
+from pyqtgraph.Qt import QtGui
 
 pg.mkQApp()
 
@@ -60,6 +61,35 @@ def test_setData():
     assert pdi.xData is None
     assert pdi.yData is None
 
+def test_opts():
+    # test that curve and scatter plot properties get updated from PlotDataItem methods
+    y = list(np.random.normal(size=100))
+    x = np.linspace(5, 10, 100)
+    pdi = pg.PlotDataItem(x, y)
+    pen = QtGui.QPen( QtGui.QColor('#FF0000') )
+    pen2 = QtGui.QPen( QtGui.QColor('#FFFF00') )
+    brush = QtGui.QBrush( QtGui.QColor('#00FF00'))
+    brush2 = QtGui.QBrush( QtGui.QColor('#00FFFF'))
+    float_value = 1.0 + 20*np.random.random()
+    pen2.setWidth( int(float_value) )
+    pdi.setPen(pen)
+    assert pdi.curve.opts['pen'] == pen
+    pdi.setShadowPen(pen2)
+    assert pdi.curve.opts['shadowPen'] == pen2
+    pdi.setFillLevel( float_value )
+    assert pdi.curve.opts['fillLevel'] == float_value
+    pdi.setFillBrush(brush2)
+    assert pdi.curve.opts['brush'] == brush2
+
+    pdi.setSymbol('t')
+    assert pdi.scatter.opts['symbol'] == 't'
+    pdi.setSymbolPen(pen)
+    assert pdi.scatter.opts['pen'] == pen
+    pdi.setSymbolBrush(brush)
+    assert pdi.scatter.opts['brush'] == brush
+    pdi.setSymbolSize( float_value )
+    assert pdi.scatter.opts['size'] == float_value
+
 def test_clear():
     y = list(np.random.normal(size=100))
     x = np.linspace(5, 10, 100)