Legend for ScatterPlotItem with ErrorBarItem

130 views
Skip to first unread message

Meow Kitty

unread,
Aug 14, 2022, 4:50:22 PM8/14/22
to pyqtgraph
Hello! I'm a chemist trying to write some software for my lab. I'm still new to coding. The software I'm writing plots unit cell parameters for a large amount of data as a scatter plot with each individual data containing error bars using pyqtgraph 0.12.4, Pyside6 6.2.3, and pandas 1.4.1. The problem I'm having is when I click on the legend to hide a particular scatter plot, the error bars are left on the plot. I'm not sure if this is a bug, but it sure is annoying! I managed to create a fix; however, that fix seems to produce another error that corrects itself but is super annoying! Here is my fix by subclassing a few pyqtgraph items:

import pyqtgraph as pg

class CustomLegendItem(pg.LegendItem):

    def __init__(self, *args, **kwargs):
       
super().__init__(*args, **kwargs)
       
self.sampleType = DIXBIPItemSample

class CustomScatterPlotItem(pg.ScatterPlotItem):
   
def __init__(self, *args, **kwargs):
        
super().__init__(*args, **kwargs)
       
self.error_item = kwargs.pop("error_item", None)

class CustomErrorBarItem(pg.ErrorBarItem):
   
def __init__(self, **opts):
       
super().__init__(**opts)
       
self.path = None

    def
setData(self, **opts):
       
self.opts.update(opts)
       
if self.isVisible():
           
self.setVisible(all(self.opts[ax] is not None for ax in ['x', 'y']))
       
else:
           
self.setVisible(not all(self.opts[ax] is not None for ax in ['x', 'y']))
       
self.path = None
       
self.update()
       
self.prepareGeometryChange()
       
self.informViewBoundsChanged()

class CustomItemSample(pg.ItemSample):
   
def __init__(self, *args, **kwargs):
       
ItemSample.__init__(self, *args, **kwargs)

   
def mouseClickEvent(self, event):
       
if event.button() == Qt.MouseButton.LeftButton:
           
visible = self.item.isVisible()
           
self.item.setVisible(not visible)
           
if isinstance(self.item.error_item, CustomErrorBarItem):
               
self.item.error_item.setVisible(not visible)
       
event.accept()
       
self.update()

I can now use a custom class for the legend, scatter plot, and error bar when adding to a plot widget. This is accomplished by passing a custom error bar item (error_item) to a custom scatter plot item as an argument.

Here is the error I receive using my fix:

Traceback (most recent call last):

  File "C:\Python310\lib\site-packages\pyqtgraph\graphicsItems\ErrorBarItem.py", line 155, in boundingRect

    self.drawPath()

  File "C:\Python310\lib\site-packages\pyqtgraph\graphicsItems\ErrorBarItem.py", line 87, in drawPath

    verticalLines = fn.arrayToQPath(xs, y1_y2, connect="pairs")

  File "C:\Python310\lib\site-packages\pyqtgraph\functions.py", line 2147, in arrayToQPath

    isfinite = np.isfinite(x) & np.isfinite(y)

ValueError: operands could not be broadcast together with shapes (92,) (94,)

A lot of times, a single error like the one above occurs multiple times in succession which causes the error bars to flicker in the plot (same error with shapes (92,) (94,) for example). It eventually stops but occurs later at a random data point. Even though this error occurs, I can still toggle the data points and error bars using the legend.

Is there a better way to add error bars to a scatter plot and be able to toggle their visibility through the legend? If not, is there anyway to update my fix to avoid that error?

Thanks for any and all help!






Patrick

unread,
Aug 15, 2022, 12:35:23 AM8/15/22
to pyqtgraph
Hi,

Since ScatterPlotItem (and every plot item type) is an indirect sub-class of QtWidgets.QGraphicsObject, you can tap into any of its functionality. It has a visibleChanged signal which will get triggered when the trace is displayed or hidden by the legend click. https://doc.qt.io/qtforpython/PySide6/QtWidgets/QGraphicsObject.html#PySide6.QtWidgets.PySide6.QtWidgets.QGraphicsObject.visibleChanged

A simple example which synchronises the error bar visibility with a scatter plot item:

import numpy as np
import pyqtgraph as pg

app = pg.mkQApp("Symbols Examples")
win = pg.GraphicsLayoutWidget(show=True)
win.resize(1000,600)

pg.setConfigOptions(antialias=True)

plot = win.addPlot()
plot.addLegend()

# Scatter plot
n = 300
scatterplot = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 120), name="things")
pos = np.random.normal(size=(2,n), scale=1e1)
spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}]
scatterplot.addPoints(spots)
plot.addItem(scatterplot)

# Error bars
errorbars = pg.ErrorBarItem(x=pos[0], y=pos[1], top=1.0, bottom=1.0, beam=0.5)
plot.addItem(errorbars)

def update_visibility():
errorbars.setVisible(scatterplot.isVisible())

# Connect to visibility signal provided by QtWidgets.QGraphicsObject, an ancestor
# class of ScatterPlotItem, update corresponding error bar visibility
scatterplot.visibleChanged.connect(update_visibility)


if __name__ == '__main__':
pg.exec()

Hopefully that will get you on the right track.
Patrick

Meow Kitty

unread,
Aug 15, 2022, 1:00:00 AM8/15/22
to pyqtgraph
Hey Patrick,

I'm new to this group thingy. I don't see my response that I wrote a minute ago, so this may be a duplicate. I apologize in advance! 

Thank you so much for your help! I was able to quickly adapt your code to a plot that contains multiple scatter plot items with their respective error bar items. Everything works like a charm! Thanks again!

-Travis :)

Reply all
Reply to author
Forward
0 new messages