Cross hair widget

2,492 views
Skip to first unread message

pyqtgraphuser

unread,
Mar 22, 2012, 9:51:38 PM3/22/12
to pyqtgraph
How would I go about adding a cross hair widget. Ideally there is
separate window that shows up which displays the x and y1, y2, y3
values for each data series. These values should be updated as you
move the mouse around. Also should get updated, if you move from one
window to another window in a multi window setup.

Luke Campagnola

unread,
Mar 23, 2012, 10:20:05 AM3/23/12
to pyqt...@googlegroups.com
If we're just talking about reporting the position of the mouse and data values (and not actually drawing a crosshair), then here are the basic steps:

  - connect to the sigMouseMoved signal from the plotItem's scene
  - convert the scene position given by this signal to data coordinates
  - update your labels accordingly

Here's an example that prints the data-coordinate position of the mouse as it moves over a plot:

import pyqtgraph as pg
p = pg.plot()

def printMousePos(pos):
    global p
    print p.plotItem.vb.mapSceneToView(pos)

p.scene().sigMouseMoved.connect(printMousePos)


 

pyqtgraphuser

unread,
Mar 23, 2012, 12:06:38 PM3/23/12
to pyqtgraph
This is nice, but it would be nice to add a cross hair to the plot
window.


On Mar 23, 10:20 am, Luke Campagnola <luke.campagn...@gmail.com>
wrote:

Luke Campagnola

unread,
Mar 29, 2012, 12:42:51 AM3/29/12
to pyqt...@googlegroups.com
I guess I never bothered to make a crosshair item since it's so simple. Here's an example:

from pyqtgraph.Qt import QtCore, QtGui
class Crosshair(QtGui.QGraphicsItem):
    def __init__(self):
        QtGui.QGraphicsItem.__init__(self)
        self.setFlag(self.ItemIgnoresTransformations)
 
    def paint(self, p, *args):
        p.setPen(pg.mkPen('y'))
        p.drawLine(-10, 0, 10, 0)
        p.drawLine(0, -10, 0, 10)
 
    def boundingRect(self):
        return QtCore.QRectF(-10, -10, 20, 20)

import pyqtgraph as pg
p = pg.plot()
ch = Crosshair()
p.addItem(ch)
ch.setPos(0.5, 0.3)

But now that it's written, I guess there's no reason not to add it to the repository :) 

pyqtgraphuser

unread,
Mar 29, 2012, 8:57:20 AM3/29/12
to pyqtgraph
The crosshair I was talking about is tied to mouse events and redraws
a full vertical and horizontal line as you move the mouse. The
intersection of the two lines would be where the mouse is. I tried
adding a mouse move event handler function to the cross hair object
but it does not seem to be called.

On Mar 29, 12:42 am, Luke Campagnola <luke.campagn...@gmail.com>

Luke Campagnola

unread,
Mar 29, 2012, 11:47:01 AM3/29/12
to pyqt...@googlegroups.com
On Thu, Mar 29, 2012 at 08:57, pyqtgraphuser <webus...@gmail.com> wrote:
The crosshair I was talking about is tied to mouse events and redraws
a full vertical and horizontal line as you move the mouse. The
intersection of the two lines would be where the mouse is.

In that case, I would just use two InfiniteLine items. In the same callback that connects to scene.sigMouseMoved, you can set the position of each line.
 
I tried
adding a mouse move event handler function to the cross hair object
but it does not seem to be called.

This is expected--mouseMoveEvent is generally only called if you are trying to drag the item: http://qt-project.org/doc/qt-4.8/qgraphicsitem.html#mouseMoveEvent

pyqtgraphuser

unread,
Mar 29, 2012, 9:28:10 PM3/29/12
to pyqt...@googlegroups.com

In that case, I would just use two InfiniteLine items. In the same callback that connects to scene.sigMouseMoved, you can set the position of each line.
   

I tried:

w = win.addPlot(row=windowNumber, col=0)

def sigMouseMovedHandler(evt):
print evt

QtCore.QObject.connect(w,QtCore.SIGNAL("sigMouseMoved"),sigMouseMovedHandler)

And that did not seem to work. How would I connect to sigMouseMoved signal. If I have three windows from addPlot, do I have to add to each plot window separately? 

megan kratz

unread,
Mar 30, 2012, 2:34:18 PM3/30/12
to pyqt...@googlegroups.com

I tried:

w = win.addPlot(row=windowNumber, col=0)

def sigMouseMovedHandler(evt):
print evt

QtCore.QObject.connect(w,QtCore.SIGNAL("sigMouseMoved"),sigMouseMovedHandler)

And that did not seem to work. How would I connect to sigMouseMoved signal?
 

There are two APIs for connecting signals in PyQt4, an older way and a newer way. You can find documentation about them here:


The connection you defined uses the old API. In order to also support using PySide (which only supports the new API) as a backend, pyqtgraph uses the new API. Additionally, while it's possible to use the old API style to connect signals that come with PyQt, signals that are defined within pyqtgraph (like sigMouseMoved) can only be connected using the new API.

The syntax for this looks like:

w.scene().sigMouseMoved.connect(sigMouseMovedHandler)

You need the w.scene() call because sigMouseMoved is an attribute of GraphicsScene, not PlotItem.

Hope that's helpful. 

Cheers,

Megan

pyqtgraphuser

unread,
Mar 30, 2012, 7:24:54 PM3/30/12
to pyqtgraph
> w.scene().sigMouseMoved.connect(sigMouseMovedHandler)

Thanks. That worked well. However, I seem to have another problem. The
evt that gets passed to sigMouseMovedHandler seems to be x and y in
pixels.

horizontleLine = pg.InfiniteLine(angle=0)
verticleLine = pg.InfiniteLine(angle=90)
w.addItem(horizontleLine)
w.addItem(verticleLine)

def mouseMoved(evt):
horizontleLine.setPos(evt.y())
verticleLine.setPos(evt.x())

w.scene().sigMouseMoved.connect(mouseMoved)

- This does not work since setPos takes coordinates in data x and y
while evt's get passed as x, y pixels. Am I doing this the correct
way?



pyqtgraphuser

unread,
Apr 2, 2012, 8:36:36 PM4/2/12
to pyqt...@googlegroups.com
Here is what I have so far. Might make sense to add this to the examples as testcrosshair.py (Once the few problems are figured out). 

Current problems:

- The center of the cross hair is not exactly on the mouse (off by around 10-20 pixels for both x and y). I'm guessing that I'm not taking into account the width of the axis labels. 
- There is a problem with redraw. If I comment out the region code below, it works fine. However, If I don't comment out the code, there are gaps which show up on the plot as I move the mouse around. 
- The y axis scale is set to 0 - 15000, even though the data is from 10000-15000, If I remove the cross hair the y axis scales correctly. 

Nice to have features:

- How would I add the x and y values on the right hand top corner of the graph?

Performance: [not that important]

- On larger datasets the cross hair movement is not super smooth, would it make sense to redraw every 100ms or higher from a performance reason? Any other ways to improve performance?

Thanks,

----------------------------
import os
import sys
import random
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.Point import Point

#genearte layout
app = QtGui.QApplication([])
win = pg.GraphicsWindow()
p1 = win.addPlot()
p2 = win.addPlot(row=1, col=0)
region = pg.LinearRegionItem()
p2.addItem(region)

#create numpy arrays
data1 = np.zeros(10000)
data2 = np.zeros(10000)

#make the numbers large to show that the xrange shows data from 10000 to all the way 0
for i in range(10000):
    data1[i] = 10000 + 3000*random.random()
    data2[i] = 15000 + 3000*random.random()
p1.plot(data1, pen="r")
p1.plot(data2, pen="g")
p2.plot(data1, pen="w")

def update():
    p1.setXRange(*region.getRegion())
region.sigRegionChanged.connect(update)
region.setRegion([1000, 2000])

#cross hair
vLine = pg.InfiniteLine(angle=90)
hLine = pg.InfiniteLine(angle=0)
p1.addItem(vLine)
p1.addItem(hLine)
vb = p1.vb

def mouseMoved(evt):
    bottomX = p1.getScale("bottom").x()
    bottomY = p1.getScale("bottom").y()
    print evt
    print evt.y(), bottomY
    if evt.y() < bottomY:
        #print dir(windows[0].getScale("bottom"))
        mousePoint = vb.mapToView(Point(evt.x()-p1.getScale("bottom").x(), evt.y()))
        vLine.setPos(mousePoint.x())
        hLine.setPos(mousePoint.y())
p1.scene().sigMouseMoved.connect(mouseMoved)


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

Luke Campagnola

unread,
Apr 3, 2012, 1:28:05 AM4/3/12
to pyqt...@googlegroups.com
I fixed a few bugs and added your example (with some changes) into the bzr repository. 

On Mon, Apr 2, 2012 at 20:36, pyqtgraphuser <webus...@gmail.com> wrote:
- The center of the cross hair is not exactly on the mouse (off by around 10-20 pixels for both x and y). I'm guessing that I'm not taking into account the width of the axis labels. 

This is because you are using the wrong map function. The mouseMoved signal gives a position in scene coordinates. To map from scene to view coordinates (where the plots live), you need to use viewBox.mapSceneToView(point). It should very rarely be necessary to account for the size/location of anything when mapping coordinates. Always be careful about which coordinate systems you are mapping from/to and check the documentation for that mapping functions.
 
- There is a problem with redraw. If I comment out the region code below, it works fine. However, If I don't comment out the code, there are gaps which show up on the plot as I move the mouse around. 

I don't have this problem .. can you send a screenshot? Exactly which lines of code do you comment out to solve the problem?
 
- The y axis scale is set to 0 - 15000, even though the data is from 10000-15000, If I remove the cross hair the y axis scales correctly. 

Good catch-we really just want to ignore the lines entirely when auto-scaling that view box. I've added this capability in the latest rev.

- How would I add the x and y values on the right hand top corner of the graph?

Check out examples/crosshair.py; I've just added a LabelItem into the window.
 
Performance: [not that important]

- On larger datasets the cross hair movement is not super smooth, would it make sense to redraw every 100ms or higher from a performance reason? Any other ways to improve performance?

In the example, I added a SignalProxy which catches all the mouseMove signals and condenses them so they are only emitted at most 60 times/sec. This should take care of a lot of the performance issues. If not, we can keep digging.

I made a few other changes to the example:
 - brought the region item out front to make it more visible
 - set the crosshair lines movable=False so they know they can't be dragged by clicking on them
 - using "if p1.sceneBoundingRect().contains(pos)" to check that the mouse is over p1

Shall I credit you in the example? (as 'pyqtgraphuser' ?)

Luke

pyqtgraphuser

unread,
Apr 3, 2012, 10:46:28 PM4/3/12
to pyqt...@googlegroups.com
Thanks for the quick turn around on your bug fixes and updates.  
 
I don't have this problem .. can you send a screenshot? Exactly which lines of code do you comment out to solve the problem?

I've attached a screenshot. If I comment out the following lines I don't see the gaps as I move the mouse around:

#region.sigRegionChanged.connect(update)
#region.setRegion([1000, 2000])

Shall I credit you in the example? (as 'pyqtgraphuser' ?)


yes, that is fine.

Thanks!
Screenshot at 2012-04-03 22:38:47.png

Luke Campagnola

unread,
Apr 3, 2012, 11:55:28 PM4/3/12
to pyqt...@googlegroups.com
On Tue, Apr 3, 2012 at 22:46, pyqtgraphuser <webus...@gmail.com> wrote:
Thanks for the quick turn around on your bug fixes and updates.  
 
I don't have this problem .. can you send a screenshot? Exactly which lines of code do you comment out to solve the problem?

I've attached a screenshot. If I comment out the following lines I don't see the gaps as I move the mouse around:

#region.sigRegionChanged.connect(update)
#region.setRegion([1000, 2000])

 
That is quite strange! Is this screenshot from the example program I posted, unmodified?
I'm going to have to ponder that.. If I understand correctly, moving the mouse over the top plot causes these black regions to appear, but mouse over the bottom plot is ok? And if you pan/scale the top plot or move the region, then it redraws correctly?

pyqtgraphuser

unread,
Apr 4, 2012, 5:51:01 AM4/4/12
to pyqt...@googlegroups.com
 
That is quite strange! Is this screenshot from the example program I posted, unmodified?
I'm going to have to ponder that.. If I understand correctly, moving the mouse over the top plot causes these black regions to appear, but mouse over the bottom plot is ok? And if you pan/scale the top plot or move the region, then it redraws correctly?

The graph is from the example program unmodified.  There is something very wrong at least on my computer. 

>> moving the mouse over the top plot causes these black regions to appear

Yes

>> but mouse over the bottom plot is ok

Yes, However, If I move the region at the bottom, The red plot does not draw properly. Lots of gaps. 

>> And if you pan/scale the top plot or move the region, then it redraws correctly?

No. Only when I minimize the window and bring it back to the front it becomes fine again. 



Luke Campagnola

unread,
Apr 4, 2012, 8:39:01 AM4/4/12
to pyqt...@googlegroups.com
On Wed, Apr 4, 2012 at 05:51, pyqtgraphuser <webus...@gmail.com> wrote:
 
That is quite strange! Is this screenshot from the example program I posted, unmodified?
I'm going to have to ponder that.. If I understand correctly, moving the mouse over the top plot causes these black regions to appear, but mouse over the bottom plot is ok? And if you pan/scale the top plot or move the region, then it redraws correctly?

The graph is from the example program unmodified.  There is something very wrong at least on my computer. 

Agreed; we need to figure out what's different between our machines.
 - operating system? 64/32 bit? 
 - PyQt / PySide version?
 - graphics chipset / driver?
 - using opengl compositing window manager (compiz / kwin) ?
 - any changes made to your local pyqtgraph library at all?


pyqtgraphuser

unread,
Apr 4, 2012, 9:29:50 AM4/4/12
to pyqt...@googlegroups.com
 - operating system? 64/32 bit? 

64 bit. Mint version 11
 
 - PyQt / PySide version?

What's the best way to find this and the installed qt version?
 
 - graphics chipset / driver?

I'll try and figure this out as well. Let me know if you know an easy way to find this out? 

 - using opengl compositing window manager (compiz / kwin) ?

Again not sure. How do I look that up?
 
 - any changes made to your local pyqtgraph library at all?

No changes. I checked out a brand new version from scratch and saw the same problems., 
 

Luke Campagnola

unread,
Apr 4, 2012, 10:16:04 AM4/4/12
to pyqt...@googlegroups.com
On Wed, Apr 4, 2012 at 09:29, pyqtgraphuser <webus...@gmail.com> wrote:
What's the best way to find this and the installed qt version?

For PyQt:
>>>  from pyqtgraph.Qt import QtCore
>>>  QtCore.PYQT_VERSION_STR
>>>  QtCore.QT_VERSION_STR

For PySide:
$ dpkg -l python-pyside


 
 - graphics chipset / driver?

I'll try and figure this out as well. Let me know if you know an easy way to find this out? 

This should tell you what your graphics hardware is:
$ lspci | grep VGA 

As far as the driver goes.. I don't have a great answer. I used to look in my /var/log/Xorg.0.log file, but now it seems like it's loading ALL of the possible video drivers, so I can't tell which one is actually in use.

 - using opengl compositing window manager (compiz / kwin) ?

I use KDE, so my experience is different. However, the internets tell me this disables compiz for Mint: "just log out and select "Gnome (No Effect)" from the Sessions List at the bottom of your screen, then Login"

So probably by default compiz is enabled, and you can try disabling it to and see if that helps. (I have had very strange problems with compositors in the past)


Maybe I build all this in to a little script..

pyqtgraphuser

unread,
Apr 6, 2012, 5:20:23 AM4/6/12
to pyqtgraph
> >>>  from pyqtgraph.Qt import QtCore
> >>>  QtCore.PYQT_VERSION_STR
> >>>  QtCore.QT_VERSION_STR

Python 2.7.2+ (default, Oct 4 2011, 20:06:09)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyqtgraph.Qt import QtCore
>>> QtCore.PYQT_VERSION_STR
'4.8.5'
>>> QtCore.QT_VERSION_STR
'4.7.3'

> For PySide:
>
> $ dpkg -l python-pyside
>

Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/
Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name
Version Description
+++-=====================================-
=====================================-
==========================================================================================
ii python-pyside
1.1.0-1~o0 Python bindings for Qt4 (big
metapackage)



> $ lspci | grep VGA
>
00:02.0 VGA compatible controller: Intel Corporation 2nd Generation
Core Processor Family Integrated Graphics Controller (rev 09)

Pedro Paiva

unread,
Jul 8, 2013, 5:54:01 AM7/8/13
to pyqt...@googlegroups.com
Can I connect the signal on a viewBox instead of a plot?
I tried both methods from the example code but it raises that viewBox doesn't have the signal... =/

Luke Campagnola

unread,
Jul 8, 2013, 10:38:46 AM7/8/13
to pyqt...@googlegroups.com
On Mon, Jul 8, 2013 at 5:54 AM, Pedro Paiva <p3k...@gmail.com> wrote:
Can I connect the signal on a viewBox instead of a plot?
I tried both methods from the example code but it raises that viewBox doesn't have the signal... =/

As Megan's reply states, the signal 'sigMouseMoved' is a member of the GraphicsScene class, not ViewBox or PlotItem. For any item, you can get the scene object by calling 'item.scene()'.


Luke

 


Em sexta-feira, 30 de março de 2012 19h34min18s UTC+1, megan escreveu:

I tried:

w = win.addPlot(row=windowNumber, col=0)

def sigMouseMovedHandler(evt):
print evt

QtCore.QObject.connect(w,QtCore.SIGNAL("sigMouseMoved"),sigMouseMovedHandler)

And that did not seem to work. How would I connect to sigMouseMoved signal?
 

There are two APIs for connecting signals in PyQt4, an older way and a newer way. You can find documentation about them here:


The connection you defined uses the old API. In order to also support using PySide (which only supports the new API) as a backend, pyqtgraph uses the new API. Additionally, while it's possible to use the old API style to connect signals that come with PyQt, signals that are defined within pyqtgraph (like sigMouseMoved) can only be connected using the new API.

The syntax for this looks like:

w.scene().sigMouseMoved.connect(sigMouseMovedHandler)

You need the w.scene() call because sigMouseMoved is an attribute of GraphicsScene, not PlotItem.

Hope that's helpful. 

Cheers,

Megan

--
-- [ You are subscribed to pyqt...@googlegroups.com. To unsubscribe, send email to pyqtgraph+...@googlegroups.com ]
---
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Pedro Paiva

unread,
Jul 8, 2013, 11:15:16 AM7/8/13
to pyqt...@googlegroups.com
hmm,
when I called with .scene() it returned that "None" type doesn't have the signal (which is very weird... I'll have another look at my code to see what is going wrong)

Pedro Paiva

unread,
Jul 10, 2013, 8:12:59 AM7/10/13
to pyqt...@googlegroups.com
Got it working! xD
Basically, I have a myViewBox to set the viewBox the way I want and grab the mouse events and I was trying to connect from inside the myViewBox class instead of connecting the myViewBox object... xP
When I moved the connect to the outside it worked!

Kitty

unread,
Dec 17, 2014, 1:28:02 AM12/17/14
to pyqt...@googlegroups.com
Hello! I overrode ViewBox wheelEvent handler, so scaling is performed with mouse wheel and pressed key "CTRL"  and translating is performed with mouse wheel. I have a plotItem with such overriden ViewBox and crosshair interaction on it. When mouse moves crosshair lines and label are visualized correctly. I would like to update crosshair elements when wheelEvent takes place. So I get wheelEvent scene pos and transform it to ViewBox corrdinates using mapSceneToView. But in this case the crosshair elements are visualized incorrectly, I guess I use wrong method to map event pos to ViewBox coordinates. I attached an example with my code. Could you please have a look at it and tell me what did I do wrong? Thank you in advance.
crosshair_example.py
Reply all
Reply to author
Forward
0 new messages