Moving vertical line in chart based off video progress

Hi,

I am working on a small Dash app that shows an average score in a chart by second for a video. I want to be able to show a moving vertical line in the chart when video played, i.e. if video is at 5th second, line should be at x=5 and etc.

My initial idea was to add shapes and update graph using JavaScript but I don’t know it well and not sure how to pass video time value to Python/Dash.

Code without video:

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(children = [

    html.Div(children = [
        html.Video(
            controls = True,
            id = 'movie_player',
        ),
    ]),
    html.Div(children = [
        dcc.Graph(
            id = 'mean_chart',
            figure = {
                'data': [go.Scatter(
                    x = [0,1,2,3,4,5,6,7,8,9,10],
                    y = [0,1,2,4,5,4,3,5,3,2,1],
                )],
                'layout': go.Layout(
                    xaxis = {
                        'title': 'Second',
                        'type': 'linear',
                    },
                    yaxis = {
                        'title': 'Average score',
                    },
                    shapes = [{
                        'type': 'line',
                        'x0': 3,
                        'y0': -1,
                        'x1': 3,
                        'y1': 6,
                    },]
                ),
            },
        ),
    ]),
])


if __name__ == '__main__':
    app.run_server(debug = True)

My graph is in @app.callback in the full app version.

JavaScript function which I am not sure is correct nor what to do with it, i.e. how to pass value to x0 and x1 in shapes :

var sec;

setInterval(function(){showChart(); }, 100);

function showChart(){
     ytplayer = document.getElementById("movie_player");
     sec = ytplayer.currentTime;
     return sec;
}

Can you suggest the best way to do it? Is it possible without JS or better way than using shapes ?

Thanks!

Hi,

I am currently doing something very similar for a project. Have you been able to make it work yet because I didn’t find much information elsewhere.

Thanks!

Hi,

I made it work kind of… It did what I wanted but I didn’t like that the vertical line didn’t move smoothly and page had to refresh while the video is playing.

Anyway, below is what I did. Maybe it will take you on the right path.

First, I installed Dash Player component:
https://github.com/plotly/dash-player

Then added a callback checking video play time every 500 miliseconds and drawing a shape based on the time on Plotly graph.

Example code:

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
import dash_player

video = 'video link goes here...'
video_rating = dash.Dash()

video_rating.layout = html.Div(
    children=[
        html.Div(dash_player.DashPlayer(id='video-player', url=video, controls=True)),
        html.Div(dcc.Graph(id='mean_chart')),
    ])


@video_rating.callback(
    Output('video-player', 'intervalCurrentTime'),
    [Input('video-player', 'currentTime')])
def update_intervalCurrentTime(value):
    return 500


@video_rating.callback(
    Output('mean_chart', 'figure'),
    Input('video-player', 'currentTime'))
def update_graph(sec):
    sec = sec if sec is not None else 0
    xvals = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    yvals = [0, 1.2, 1.4, 1.5, 1.1, 0, -0.1, -0.5, 0.1, 0.25]
    return {
        'data': [go.Scatter(x=xvals, y=yvals, line={'color': 'rgb(244,173,179)'})],
        'layout': go.Layout(
            margin={'l': 40, 'r': 30, 't': 40, 'b': 50, 'pad': 2},
            height=400,
            shapes=[{
                'type': 'line',
                'x0': sec,
                'y0': min(yvals) - 1,
                'x1': sec,
                'y1': max(yvals) + 1,
                'line': {'color': 'rgb(55, 128, 191)', 'width': 0 if sec == 0 else 2},
            }]
        )
    }


if __name__ == '__main__':
    video_rating.run_server(debug=False)

Hope it helps.

In case this is relevant for anyone, I had a similar problem and to make the movement smoother you can update the intervalCurrentTime property of the DashPlayer to be below the default value of 100ms. I implemented a callback for updating the position in js, which does not require a page refresh. The code is here: Update position of vertical line - #2 by Turing