Search code examples
pythonpyqtvispy

How to persuade visuals_at to look beyond ViewBox?


I'm currently evaluating vispy for my interactive plotting needs. While it feels a bit beta-ish I'm rather impressed with its speed. Also API design-wise it looks promising.

One feature I'd need to work is picking plot elements with the mouse. There is one example in the distribution (0.6.4) that promises to do precisely that: examples/demo/scene/picking.py. Unfortunately it doesn't work for me.

It displays a single window containing a graph with multiple lines. I can interact with the plot as whole, i.e. zoom and shift, but I cannot select individual lines.

If I monkey-debug the relevant piece of code (print statement is mine, full example is at github):

@fig.connect
def on_mouse_press(event):
    global selected, fig
    if event.handled or event.button != 1:
        return
    if selected is not None:
        selected.set_data(width=1)
    selected = None
    for v in fig.visuals_at(event.pos):
        print(v)
        if isinstance(v, vp.LinePlot):
            selected = v
            break
    if selected is not None:
        selected.set_data(width=3)
        update_cursor(event.pos)

I get <ViewBox at 0x...> no matter where I click. fig is a vispy.plot.Fig instance which is not well documented.

How can I make this work, i.e. make visuals_at see beyond the ViewBox and find the actual LinePlot instances?


Solution

  • There is a workaround to make the view non-interactive before calling visuals_at. Afterwards the view can be set to interactive again.

    This workaround can be found here in a google groups message workaround

    The post is from the year 2015, so the problem seems to have been known for some time.

    Code

    So add to the code

    plt.view.interactive = False
    

    before calling fig.visuals_at and afterwards do

    plt.view.interactive = True
    

    Afterwards the code for on_mouse_press should look like this:

    def on_mouse_press(event):
        global selected, fig
        if event.handled or event.button != 1:
            return
        if selected is not None:
            selected.set_data(width=1)
        selected = None
        plt.view.interactive = False
        for v in fig.visuals_at(event.pos):
            if isinstance(v, vp.LinePlot):
                selected = v
                break
        plt.view.interactive = True
        if selected is not None:
            selected.set_data(width=3)
            update_cursor(event.pos)
    

    Test

    Test Output