Repository: pymeasure/pymeasure

Issue:
Wrong right errorbar
More by accident I stumbled upon a bug/typo/copy-paste-error in ```update_data``` of the ```ResultsCurve``` class when looking for something unrelated. The following code-block sets errorbar options if enabled:

```
if hasattr(self, '_errorBars'):
            self._errorBars.setOpts(
                x=data[self.x],
                y=data[self.y],
                top=data[self.yerr],
                bottom=data[self.yerr],
                left=data[self.xerr],
                right=data[self.yerr],
                beam=max(data[self.xerr], data[self.yerr])
            )
```
I think the line ```right=data[self.yerr]``` should be changed to ```right=data[self.xerr]```. Can someone confirm my suspicion? I have never worked with errorbars and I don't have an example to test it ready.


Discussion:
I checked further, this feature does not seems necessary at this point in pymeasure as the only point where a ResultsCurve is created is in ```PlotterWindow```. There the xerr and yerr keywords are not given, so this error is without consequence in the behaviour of pymeasure.
You're indeed right that the `self.yerr` for right should've been `self.xerr` (though when diving into this it seems that using the `height` and `width` arguments also would've been nicer than the `top`, `bottom`, `left`, and `right` arguments).

I also noted that, at this moment, the entire feature does not work as far as I can tell (though I have never tried it before, so I can't tell if that is due to a recent change somewhere). If you get the `xerr` and `yerr` defined (you need to override the `new_curve` method for this I think), you get an error concerning the `beam` argument and (when commenting the `beam` argument) the errorbars are not added to the graph.
I looked in to this a bit more. I came to the same conclusion(s) as you did. I think the `beam` argument at least for the further development can be set to 0.5

I managed to have an example now on a branch on my fork which at least works. One thing that I found missing was the fact that the errorbar was never added to the `PlotWidget` which I for now solved by appending the constructor of the `PlotterWindow` part

```
self.curve = ResultsCurve(plotter.results, columns[0], columns[1],
                                  pen=pg.mkPen(color=pg.intColor(0), width=linewidth), antialias=False,xerr=xerr,yerr=yerr)
self.plot.addItem(self.curve)
if hasattr(self.curve, '_errorBars'):
    self.plot.addItem(self.curve._errorBars)
```

Is it worth following this path? If so one should probably find some scenarios where this could be useful to understand how the errorbar values are obtained in an experiment and where (on which level) they should be implemented.
I think we either have to fix it or have to remove it. I'm not entirely sure what the best approach is. It seems that no-one has used this feature for quite some time; otherwise I expect that someone would've raised this issue before.
Do you use the error-bars in your experiments, or did you just accidentally happen upon the feature?

If we're fixing it, the first step would be to fix the parts that are incorrect (in the way you suggested). The next step would be to see how errorbar values are obtained in experiments and if/how the should be implemented more conveniently.
I personally never used them, it was really just by accident that I noticed this mere typo.

I can only imagine an experiment which runs multiple times to have the standard deviation of the measured quantity as an error on the average. From my personal experience I don't see any real benefit of having errorbars in a live plot. If I am interested in the size of the error on the measured quantity directly in the lab, I can (and should) put it as a result and then I'm able to see it with the Plotter that is already implemented.

Others are of course welcome to put forward their use cases etc., for my personal use of pymeasure I will never use errorbars.
Ahh, check.

I would agree that I don't see much use in having error-bars in live plots; the only real use case I can come up with would involve instruments that can return an error-value directly, but even in that case I would think that is only relevant for post-processing, and not really useful in a live plot.

As such I would vote for removing the error-bar feature; unless of course someone has a use case for them, but then it needs more than simply fixing that part of the code.
Yeah, I agree, remove it.

Resolving patch:
diff --git a/pymeasure/display/curves.py b/pymeasure/display/curves.py
--- a/pymeasure/display/curves.py
+++ b/pymeasure/display/curves.py
@@ -44,22 +44,17 @@ def _greyscale_colormap(x):
 
 
 class ResultsCurve(pg.PlotDataItem):
-    """ Creates a curve loaded dynamically from a file through the Results
-    object and supports error bars. The data can be forced to fully reload
-    on each update, useful for cases when the data is changing across the full
-    file instead of just appending.
+    """ Creates a curve loaded dynamically from a file through the Results object. The data can
+    be forced to fully reload on each update, useful for cases when the data is changing across
+    the full file instead of just appending.
     """
 
-    def __init__(self, results, x, y, xerr=None, yerr=None,
-                 force_reload=False, **kwargs):
+    def __init__(self, results, x, y, force_reload=False, **kwargs):
         super().__init__(**kwargs)
         self.results = results
         self.pen = kwargs.get('pen', None)
         self.x, self.y = x, y
         self.force_reload = force_reload
-        if xerr or yerr:
-            self._errorBars = pg.ErrorBarItem(pen=kwargs.get('pen', None))
-            self.xerr, self.yerr = xerr, yerr
 
     def update_data(self):
         """Updates the data by polling the results"""
@@ -70,18 +65,6 @@ def update_data(self):
         # Set x-y data
         self.setData(data[self.x], data[self.y])
 
-        # Set error bars if enabled at construction
-        if hasattr(self, '_errorBars'):
-            self._errorBars.setOpts(
-                x=data[self.x],
-                y=data[self.y],
-                top=data[self.yerr],
-                bottom=data[self.yerr],
-                left=data[self.xerr],
-                right=data[self.yerr],
-                beam=max(data[self.xerr], data[self.yerr])
-            )
-
 
 # TODO: Add method for changing x and y
 
@@ -159,27 +142,21 @@ def round_up(self, x):
 
 
 class BufferCurve(pg.PlotDataItem):
-    """ Creates a curve based on a predefined buffer size and allows
-    data to be added dynamically, in additon to supporting error bars
+    """ Creates a curve based on a predefined buffer size and allows data to be added dynamically.
     """
 
     data_updated = QtCore.QSignal()
 
-    def __init__(self, errors=False, **kwargs):
+    def __init__(self, **kwargs):
         super().__init__(**kwargs)
-        if errors:
-            self._errorBars = pg.ErrorBarItem(pen=kwargs.get('pen', None))
         self._buffer = None
 
     def prepare(self, size, dtype=np.float32):
         """ Prepares the buffer based on its size, data type """
-        if hasattr(self, '_errorBars'):
-            self._buffer = np.empty((size, 4), dtype=dtype)
-        else:
-            self._buffer = np.empty((size, 2), dtype=dtype)
+        self._buffer = np.empty((size, 2), dtype=dtype)
         self._ptr = 0
 
-    def append(self, x, y, xError=None, yError=None):
+    def append(self, x, y):
         """ Appends data to the curve with optional errors """
         if self._buffer is None:
             raise Exception("BufferCurve buffer must be prepared")
@@ -190,19 +167,6 @@ def append(self, x, y, xError=None, yError=None):
         self._buffer[self._ptr, :2] = [x, y]
         self.setData(self._buffer[:self._ptr, :2])
 
-        # Set error bars if enabled at construction
-        if hasattr(self, '_errorBars'):
-            self._buffer[self._ptr, 2:] = [xError, yError]
-            self._errorBars.setOpts(
-                x=self._buffer[:self._ptr, 0],
-                y=self._buffer[:self._ptr, 1],
-                top=self._buffer[:self._ptr, 3],
-                bottom=self._buffer[:self._ptr, 3],
-                left=self._buffer[:self._ptr, 2],
-                right=self._buffer[:self._ptr, 2],
-                beam=np.max(self._buffer[:self._ptr, 2:])
-            )
-
         self._ptr += 1
         self.data_updated.emit()
 
diff --git a/pymeasure/display/plotter.py b/pymeasure/display/plotter.py
--- a/pymeasure/display/plotter.py
+++ b/pymeasure/display/plotter.py
@@ -36,8 +36,7 @@
 
 
 class Plotter(StoppableThread):
-    """ Plotter dynamically plots data from a file through the Results
-    object and supports error bars.
+    """ Plotter dynamically plots data from a file through the Results object.
 
     .. seealso::
 


Test patch: