Moving points on a PlotItem subclass (not ScatterPlot/GraphItem)

906 views
Skip to first unread message

Jonathan Rogers

unread,
Mar 16, 2016, 2:00:48 AM3/16/16
to pyqtgraph
Does anyone know of any examples showing how to move a point on a PlotItem subclass? 

The only examples I can find of moving/dragging points are for GraphItem subclasses which utilize a scatter plot. The method for which data is inputted into a GraphItem (def setData()) seems like it would be costly/confusing for multiple plot lines/curves having many points so I'd like to avoid it if possible.

I just started using pyqtgraph a couple of days ago so if I've missed something please let me know! Eventually this will be wrapped into a widget and then into a PyQt program. I need to be able to plot up many curves and points, some of which the user will be able to modify which will then modify the protected curves (a simplified example would be a bezier where the user is able to interact with the control points only).

Thanks for the help!
-Jonathan

Jonathan Rogers

unread,
Mar 16, 2016, 2:26:49 AM3/16/16
to pyqtgraph
Also, I've played around with it on my own but i've found out the following:

I can use PlotItem and cycle through self.dataItems. I can use each item's .scatter.pointsAt(pos) method but the position given by ev.buttonDownPos() looks to be in pixels(?) so I can't really use the pointsAt() method.

In contrast, with the GraphItem example it appears the ev.buttonDownPos() returns a value in the local coordinate system which would allow the item.scatter.pointsAt(pos) method to function properly I believe.

Wondering if I missed an easier way to go about this?

Jonathan Rogers

unread,
Mar 16, 2016, 11:31:18 PM3/16/16
to pyqtgraph
Another update:

I figured out how to convert the x,y coords from pixels to the local coordinates by using the PlotItem's self.vb.mapSceneToView(pos) method. 

Now I have an issue when I look at the scatter.pointsAt(pos) method. It seems it's using self.pixelWidth() and self.pixelHeight() to determine if a point is close enough to return. My question is does this value get converted to local coordinates? Also, how do I modify this value easily? Can I do it from a global perspective?

Thanks for your help!


On Tuesday, March 15, 2016 at 11:00:48 PM UTC-7, Jonathan Rogers wrote:

Jonathan Rogers

unread,
Mar 17, 2016, 12:36:06 PM3/17/16
to pyqtgraph
Figured it out. It DOES seem to lag a little behind the cursor but it works. If anyone has a solution for the small amount of lag, I expect its on my end rather than actual plot lag but I do not know. Perhaps it's the new position and how I prescribe it?

Does anyone have any suggestions on how to make this a little cleaner? I'm worried when I start doing multiple lines and calculations in the background that the small offset I see between mouse and point will grow as I use more resources... Any help is appreciated.

For reference... Here is a simplified example that I've commented pretty heavily to show what is going on. Thanks!!

import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np

# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)

class Plot(pg.PlotItem):

    def __init__(self):
        pg.PlotItem.__init__(self)

       
# Initialize the class instance variables.
        self.dragPoint = None
        self.dragIndex = -1
        self.dragOffset = 0
        self.plot_item_control = None

        # Create initial plot information for this example.
        self._create_stuff()

   
def _create_stuff(self):
        x = np.array([1.0, 2.0, 4.0, 3.0])
        y
= np.array([1.0, 3.0, 3.0, 1.0])

       
self.plot_item_control = self.plot(x,y, symbolBrush=(255,0,0), symbolPen='w')
       
pass

    def mouseDragEvent(self, ev):
        # Check to make sure the button is the left mouse button. If not, ignore it.
        # Would have to change this if porting to Mac I assume.
        if ev.button() != QtCore.Qt.LeftButton:
            ev.ignore()
           
return

        if ev.isStart():
            # We are already one step into the drag.
            # Find the point(s) at the mouse cursor when the button was first
            # pressed:
            # pos = ev.buttonDownPos()
            pos = ev.buttonDownScenePos()
           
# Switch position into local coords using viewbox
            local_pos = self.vb.mapSceneToView(pos)

           
for item in self.dataItems:
                new_pts = item.scatter.pointsAt(local_pos)
               
if len(new_pts) == 1:
                    # Store the drag point and the index of the point for future reference.
                    self.dragPoint = new_pts[0]
                   
self.dragIndex = item.scatter.points().tolist().index(new_pts[0])
                   
# Find the initial offset of the drag operation from the current point.
                    # This value should allow for the initial mismatch from cursor to point to be accounted for.
                    self.dragOffset = new_pts[0].pos() - local_pos
                   
# If we get here, accept the event to prevent the screen from panning during the drag.
                    ev.accept()
       
elif ev.isFinish():
            # The drag is finished. Reset the drag point and index.
            self.dragPoint = None
            self.dragIndex = -1
            return
        else:
            # If we get here, this isn't the start or end. Somewhere in the middle.
            if self.dragPoint is None:
                # We aren't dragging a point so ignore the event and allow the panning to continue
                ev.ignore()
               
return
            else:
                # We are dragging a point. Find the local position of the event.
                local_pos = self.vb.mapSceneToView(ev.scenePos())

               
# Update the point in the PlotDataItem using get/set data.
                # If we had more than one plotdataitem we would need to search/store which item
                # is has a point being moved. For this example we know it is the plot_item_control object.
                x,y = self.plot_item_control.getData()
               
# Be sure to add in the initial drag offset to each coordinate to account for the initial mismatch.
                x[self.dragIndex] = local_pos.x() + self.dragOffset.x()
                y
[self.dragIndex] = local_pos.y() + self.dragOffset.y()
               
# Update the PlotDataItem (this will automatically update the graphics when a change is detected)
                self.plot_item_control.setData(x, y)

       
pass

    pass

''' Plot '''
# Create the GUI application so we can call .show() and have it handled correctly.
app = QtGui.QApplication([])
# Create a GraphicsLayoutWidget to hold the PlotItem
w = pg.GraphicsLayoutWidget()
# Create example PlotItem subclass
g = Plot()
# Add the Plot() to the layout widget.
w.addItem(g)
# Show the magic.
w.show()

## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys
   
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

- Jonathan



On Tuesday, March 15, 2016 at 11:00:48 PM UTC-7, Jonathan Rogers wrote:

Jonathan Rogers

unread,
Mar 17, 2016, 12:38:38 PM3/17/16
to pyqtgraph
Sorry, it seems the code I've inputted above hit a character limit. The code file is attached. Thanks again!

- Jonathan
                <span style="color: #000;" class="st
simplified_plotitem_movement.py

Bob

unread,
Jun 15, 2019, 1:37:19 PM6/15/19
to pyqtgraph
Fantastic, thanks for sharing this. I was just in the process of trying to make something like this. I wonder if you've worked on it further in any way since posting this? e.g. do you have a class that allows interactive addition and removal of points?
Reply all
Reply to author
Forward
0 new messages