Synchronizing a graph with a video

Hi

I have a dash page that shows a video and several graphs that relate to the video.

I’d like to indicate the video time in the graphs with a vertical line that moves as the video is played / scrubbed.

I know I can get a callback for the current video time using the Dash Player component.

And I can create a vertical line at a given X axis position using Horizontal and vertical lines and rectangles in Python.

But I don’t know how I can (efficiently) synchronize the two. I’ll want to update the location of the vertical line > 10Hz for each graph.

Since the vertical line is a shape (and therefore part of the Graph’s layout) and not Graph data, I can’t use the Graph’s extendData property as a callback output.

Can I somehow update the location of the vertical line without re-drawing the entire graph?

I guess this would also ideally happen in a clientside callback since all the data is available on the client side and I don’t want to be communicating back and forth this frequently.

Any suggestions are welcome, thanks!

Hi @ndepal welcome back to the forums.

Interesting use case. Two things come into my mind: clientside callbacks for updating the v_line or the Patch() method from partial updates.

In the end, v_lines are just annotations in the fig.layout so you could update them easily.

pretty much like this:

import dash
import plotly.graph_objects as go
from dash import Input, Output, Patch, callback, html, dcc


app = dash.Dash(__name__)
fig = go.Figure(
    go.Scatter(x=[0, 1, 2], y=[4, 5, 6]),
)

app.layout = html.Div([
    html.Button(
        "Update Sublot",
        id="update-trace"
    ),
    dcc.Graph(
        figure=fig,
        id="my-fig"
    ),
])


@callback(
    Output("my-fig", "figure"),
    Input("update-trace", "n_clicks"),
    prevent_initial_call=True
)
def my_callback(clicks):

    # Creating a Patch object
    patched_figure = Patch()
    x = clicks * 0.1
    patched_figure['layout']['shapes'] = [{'type': 'line', 'x0': x, 'x1': x, 'xref': 'x', 'y0': 0, 'y1': 1, 'yref': 'y domain'}]
    return patched_figure


if __name__ == "__main__":
    app.run(debug=True)

mred cscb

1 Like

Thanks for your response, @AIMPED !

Using Patch() is interesting, I was not familiar with this. But this still requires client - server communication for every update, which I don’t think is suitable for me.

In the end, v_lines are just annotations in the fig.layout so you could update them easily.

Can you elaborate on this? I have not been able to find any documentation or example about how to update a graph layout clientside (without updating the entire figure).

Can I use Patch in a clientside callback?

The example I showed above uses patch and changes the v_line. Exactly what you want to do.

Patch is used server-side only. I always imagine this as a instruction which is send to the client and the changes actually happen client-side.

If I find the time tomorrow, I’ll provide your the client-side callback.

But in the meantime you could use the forum search on how to change figures client-side, I’m sure you’ll find something.

EDIT: here the client-side example:

import dash
from dash import dcc, Input, Output, State
import dash_bootstrap_components as dbc
import plotly.graph_objects as go

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = dbc.Container([
    dbc.Col(
        dbc.Row([
            dcc.Graph(
                id='graph',
                figure=go.Figure(
                    data=go.Scatter(
                        x=[*range(100)],
                        y=[*range(100)],
                        mode='markers'
                    ),
                    layout={'yaxis_range': [0, 20]}
                )
            )
        ]),
    ),
    dbc.Row([
        dbc.Col(
            dbc.Button(
                children='move line',
                id='btn'
            )
        ),
    ]),

])

app.clientside_callback(
    """
    function(value, figure) {
        newFig = JSON.parse(JSON.stringify(figure))
        newFig['layout']['shapes']=[{'type': 'line', 'x0': value, 'x1': value, 'xref': 'x', 'y0': 0, 'y1': 1, 'yref': 'y domain'}]
        return newFig
    }
    """,
    Output('graph', 'figure'),
    Input('btn', 'n_clicks'),
    State('graph', 'figure'),
    prevent_initial_call=True
)

if __name__ == "__main__":
    app.run(debug=True, port=8055)

mred cscb

1 Like