Interactive quiver plot (figure_factory with ipython / ipywidgets / FigureWidget)

I’m looking for a way to render a quiver plot interactive with the use of ipywidgets.

Say for example I have the following code

import numpy as np
import plotly.figure_factory as ff
import plotly.graph_objects as go
import ipywidgets as widgets

# Some vectors data
x=[0, 1, 2]
y=[0, 1, 0]
u=[-1, 2, 1]
v=[-0.2, 0.5, -1.7]

# the quiver plot
fig = ff.create_quiver(x, y, u, v,
                        scale=0.3,
                        arrow_scale=.30,
                        name='quiver',
                        line=dict(width=1))

# some extra data
xx=2*np.random.rand(5)
yy=0.2+1.5*np.random.rand(5)
trace=dict(type='scatter', x=xx,  y=yy,  mode='markers',
            marker=dict(color='red',  size=6)
            )

# the interactive plot
fw = go.FigureWidget(data=[fig.data[0], trace], layout=fig.layout)


# amplitude slider (i.e. A*u and A*v)
A = widgets.FloatSlider(min=0, max=3, step=0.5, description="Amplitude",
                        continuous_update=True)


# the callback to update the graph when changing amplitude
def update(change):
    pass
    # ?????????????????????????

# Link the slider and the callback
A.observe(update, names='value')

# Display slider + figure
display(widgets.VBox([A, fw]))

How could I (efficiently) update the figure given the value of the amplitude (say to multiply the length of each vector for example)? (I know that I can change the length of the arrows with the attribute β€˜scale’, but this is just a dummy example, the idea is to apply some more complicated transformation on my vectors eventually)

Thank you so much in advance for your help :slight_smile: , and for this fantastic library. I really love all the possibilities it offers for scientific computing :star_struck:

Anyone? :pray::pray:

Hi @Dryou ,

the quiver figure factory, is in fact just a helper python function that creates automatically a scatter plot where the lines look like arrows. In order to update the trace, you need to recreate it every time an input parameter changes.

I took your dummy example and slightly modified it to show you:

import numpy as np
import plotly.figure_factory as ff
import plotly.graph_objects as go
import ipywidgets as widgets

# Some vectors data
x=[0, 1, 2]
y=[0, 1, 0]
u=[-1, 2, 1]
v=[-0.2, 0.5, -1.7]


# some extra data
xx=2*np.random.rand(5)
yy=0.2+1.5*np.random.rand(5)
trace=dict(type='scatter', x=xx,  y=yy,  mode='markers',
            marker=dict(color='red',  size=6)
            )

# the interactive plot
fw = go.FigureWidget(data=[go.Scatter(), trace,], layout=fig.layout)


# amplitude slider (i.e. A*u and A*v)
widgets_dict = dict(
    scale = widgets.FloatSlider(min=0, max=1, step=0.1, value=0.3, description="scale"),
    arrow_scale = widgets.FloatSlider(min=0, max=1, step=0.1, value=0.2, description="arrow_scale"),
)


# the callback to update the graph when changing amplitude
def update_fig(scale=0.3, arrow_scale=0.2):
    # create the quiver plot
    fig = ff.create_quiver(x, y, u, v,
                        scale=scale,
                        arrow_scale=arrow_scale,
                        name='quiver',
                        line=dict(width=1))
    fw.data[0].update(fig.data[0])
        
# Link the slider and the callback
interact = widgets.interactive(update_fig, **widgets_dict)

# trigger the callback once with the set widget values
interact.update()

# Display slider widgets + figure
display(widgets.VBox([*widgets_dict.values()]), fw)

hope it helps :wink:

Alex -

This is amazing. Thank you very much for your help :slight_smile:

I edited a little bit the code for future reference, in case people would want to reproduce it:

# grid range
x = np.arange(-2, 2.1, 0.5)
y = np.arange(-2, 2.1, 0.5)

# the grid itself (points+vectors)
xx, yy = np.meshgrid(x, y)
xx, yy = xx.ravel(), yy.ravel()
u = np.zeros(shape=xx.shape)
v = np.zeros(shape=xx.shape)


# slider to modify vectors (--> u+u_ and v+v_)
widgets_dict = dict(
    u_=widgets.FloatSlider(min=-2, max=2, step=0.2, value=0.3, description="u_"),
    v_=widgets.FloatSlider(min=-2, max=2, step=0.2, value=0.2, description="v_"),
)

trace = dict(type="scatter", x=xx, y=yy, mode="markers", marker=dict(size=1))

# the interactive plot
fw = go.FigureWidget(
    data=[
        go.Scatter(),
        trace,
    ]
)  # , layout=fig.layout)

# the callback to update the graph when changing amplitude
def update_fig(u_=0.3, v_=0.2):
    # create the quiver plot
    fig = ff.create_quiver(xx, yy, u + u_, v + v_, name="quiver", line=dict(width=1))
    fw.data[0].update(fig.data[0])


# Link the slider and the callback
interact = widgets.interactive(update_fig, **widgets_dict)

# trigger the callback once with the set widget values
interact.update()

# Display slider widgets + figure
display(widgets.VBox([*widgets_dict.values()]), fw)

Which gives the following: :star_struck: