so I am working with relatively large arrays (size (13, 8192)) to plot some Figures on a website. It has already been implemented like this, so changes are difficult to make.
As I am running out of memory using the local storage of the browser, I have to use directly a given complex NumPy array and then split it into the real and imaginary parts in another Callback. The problem is that I can't JSON serialize complex-like arrays. Does someone know what can I do to "save" this sort of array using the dcc.Store component of Dash? Thanks in advance.
Here is an example of the code (it is a really short version of it).
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objects as go
import numpy as np
app = dash.Dash(__name__)
T0 = 1E-12 # duration of input
N = 8192 # number of points
dt = 750*T0/N
T = np.arange(-N/2, N/2)*dt
m = 1
C = 0
def envelopef(T,T0,C,m):
U = (np.exp(-((1+1j*C)/2)*((T/T0)**(2*m)))).astype(complex)
UI = np.absolute(U)**2
return U, UI
z = np.arange(-10,10)
U, UI = envelopef(T,T0,C,m)
scatter1 = go.Scatter(x=T/T0,y=UI)
figure1 = go.Figure(data=[scatter1]).update_layout( )
env_graph = dcc.Graph(id='envelopesss',
animate=True,
figure=figure1.update_layout(width=600, height=600,
xaxis = dict(range = [-8, 8])))
M_slider = dcc.Slider(
id='m_slider',
min=1,
max=10,
step=1,
value=m,
marks={
1: {'label': '1'},
10: {'label': '10'}},
)
app.layout = html.Div([
M_slider,
dcc.Store(id='session', storage_type='local'),
dcc.Loading(id="loading1",children=[html.Div([env_graph]) ],type="circle",),
])
#app.callback(
Output("loading1", "children"),
Output("session", "data"),
[Input("m_slider", "value")])
def update_bar_chart(mn):
U, UI = envelopef(T,T0,C,mn)
phase = np.angle(U)
scatter1 = go.Scatter(x=T/T0,y=UI)
figure1 = go.Figure(data=[scatter1]).update_layout(width=600, height=600,
xaxis = dict(range = [-8, 8]))
data = {'u': U , 'ui':UI, 'up': phase}
env_graph = dcc.Graph(figure=figure1)
return env_graph, data
app.run_server(debug=True)
You might want to take a look at the ServersideOutput component from dash-extensions. It keeps the data server side (which should improve the performance of your app), and since the default serializer is pickle, complex-valued arrays work out of the box. You can install it via pip,
pip install dash-extensions==0.0.66
To enable use of serverside outputs, replace the app initialization code by
from dash_extensions.enrich import DashProxy, html, dcc, Input, Output, ServersideOutput, ServersideOutputTransform
app = DashProxy(__name__, transforms=[ServersideOutputTransform()])
Next, replace the Output by a ServersideOutput,
#app.callback(
Output("loading1", "children"),
ServersideOutput("session", "data"),
[Input("m_slider", "value")])
That's it. Your app should now work. For completeness, here is the full app code,
import plotly.graph_objects as go
import numpy as np
from dash_extensions.enrich import DashProxy, html, dcc, Input, Output, ServersideOutput, ServersideOutputTransform
app = DashProxy(__name__, transforms=[ServersideOutputTransform()])
T0 = 1E-12 # duration of input
N = 8192 # number of points
dt = 750 * T0 / N
T = np.arange(-N / 2, N / 2) * dt
m = 1
C = 0
def envelopef(T, T0, C, m):
U = (np.exp(-((1 + 1j * C) / 2) * ((T / T0) ** (2 * m)))).astype(complex)
UI = np.absolute(U) ** 2
return U, UI
z = np.arange(-10, 10)
U, UI = envelopef(T, T0, C, m)
scatter1 = go.Scatter(x=T / T0, y=UI)
figure1 = go.Figure(data=[scatter1]).update_layout()
env_graph = dcc.Graph(id='envelopesss',
animate=True,
figure=figure1.update_layout(width=600, height=600,
xaxis=dict(range=[-8, 8])))
M_slider = dcc.Slider(
id='m_slider',
min=1,
max=10,
step=1,
value=m,
marks={
1: {'label': '1'},
10: {'label': '10'}},
)
app.layout = html.Div([
M_slider,
dcc.Store(id='session', storage_type='local'),
dcc.Loading(id="loading1", children=[html.Div([env_graph])], type="circle", ),
])
#app.callback(
Output("loading1", "children"),
ServersideOutput("session", "data"),
[Input("m_slider", "value")])
def update_bar_chart(mn):
U, UI = envelopef(T, T0, C, mn)
phase = np.angle(U)
scatter1 = go.Scatter(x=T / T0, y=UI)
figure1 = go.Figure(data=[scatter1]).update_layout(width=600, height=600,
xaxis=dict(range=[-8, 8]))
data = {'u': U, 'ui': UI, 'up': phase}
env_graph = dcc.Graph(figure=figure1)
return env_graph, data
app.run_server(port=7777)
Related
I want to create a graphical terminal like a tradingview. For this, I use plotly dash to make the display as comfortable as possible. But a situation arose that I have been unable to resolve for several days now.
Now my code looks like this:
import asyncio import datetime as DT from threading import Thread
import dash import numpy as np import plotly.graph_objs as go from dash import dcc, html from dash.dependencies import Input, Output
# ======================================================================================================================
# === Create DATA ======================================================================================================
# ======================================================================================================================
Start_Time = DT.datetime.timestamp(DT.datetime.now().replace(second=0, microsecond=0)) * 1000 X_time = np.array([]) Y_one = np.array([]) Y_two = np.array([]) Y_three = np.array([])
async def create_connection():
from random import randrange
import time
count = 0
global X_time
global Y_one
global Y_two
global Y_three
while count < 1000000:
X_time = np.append(X_time, count)
Y_one = np.append(Y_one, randrange(10000))
Y_two = np.append(Y_two, randrange(10000))
Y_three = np.append(Y_three, randrange(10000))
count += 1
time.sleep(1)
def async_main_wrapper():
"""Not async Wrapper around async_main to run it as target function of Thread"""
asyncio.run(create_connection())
# ======================================================================================================================
# === Create DASH ======================================================================================================
# ======================================================================================================================
X = np.array([0]) Y = np.array([0])
external_stylesheets = ['https://fonts.googleapis.com/css2?family=Montserrat+Alternates:wght#400;600&display=swap'] app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
colors = {
'background': '#FDFEFE',
'text': '#424242' }
app.layout = html.Div(style={'backgroundColor': colors['background']}, children=[
html.H1(
children='Main graph',
style={
'textAlign': 'left',
"fontFamily": "Montserrat Alternates",
"fontWeight": "600",
'color': colors['text']
}),
dcc.Graph(id='live_graph', style={'height': '700px', 'width': '100%'}, animate=True, config={'scrollZoom':True}),
dcc.Interval(
id='graph_update',
interval=1000 * 1
), ] )
def create_area(line_Y_one, line_Y_two, line_Y_three, X_time):
fig = {'data': [line_Y_one, line_Y_two, line_Y_three],
'layout': go.Layout(xaxis=dict(range=[min(X_time), max(X_time)], rangeslider=dict(visible=True)),
yaxis=dict(tickfont=dict(color='rgba(0, 188, 212, 0.0)')),
yaxis2=dict(overlaying="y", tickfont=dict(color='rgba(0, 188, 212, 0.0)')),
yaxis3=dict(overlaying="y"),
)}
return fig
#app.callback(Output('live_graph', 'figure'),
Input('graph_update', 'n_intervals')) def update_graph_scatter(graph_update):
global X_time
global Y_one
global Y_two
global Y_three
clr_green = 'rgba(139, 195, 74, '
clr_red = 'rgba(220, 20, 60, '
clr_brown = 'rgba(93, 64, 55, 1.0)'
line_Y_one = go.Scatter(
x=X_time,
y=Y_one,
name='Y_one',
line=dict(color=clr_green + '0.0)'),
fillcolor=clr_green + '0.3)',
fill='tozeroy',
yaxis='y1'
)
line_Y_two = go.Scatter(
x=X_time,
y=Y_two,
name='Y_two',
line=dict(color=clr_red + '0.0)'),
fillcolor=clr_red + '0.3)',
fill='tozeroy',
yaxis='y1'
)
line_Y_three = go.Scatter(
name='Y_three',
x=X_time,
y=Y_three,
line=dict(color=clr_brown),
yaxis='y3'
)
return create_area(line_Y_one, line_Y_two, line_Y_three, X_time)
# ======================================================================================================================
# === Start ===========================================================================================================
# ======================================================================================================================
if __name__ == '__main__':
th = Thread(target=async_main_wrapper)
th.start()
app.run_server(host='localhost', port=8080, debug=True, use_reloader=False)
th.join()
When starting the graph, everything goes well, it is updated once a second, as specified in the settings:
https://i.postimg.cc/L8yCj6Dw/normal.gif
But if I switch to another tab or window (i.e. I don't look at the chart), then when I return, the chart starts to update quickly, compressing and decompressing like a spring:
https://i.postimg.cc/BvXhp9w6/glitch.gif
How can I solve this situation?
And as an addition: could you recommend a good raftly dash guide that
will show solutions to similar problems?
I'm not clear how I could dynamically create multiple charts at once - or if that is not possible then how I could loop through a list of values using a single callback.
For example in the code below list of continents is a a list of filter options. Is it possible to basically make it so when this page loads, I see 5 charts automatically?
Currently, what I'm doing is I have to type 5 #app.callback...make_scatter_plot(option=_dropdown_value) which ends up with a million lines of code in my file and makes it hard to read even though everything is doing the same thing.
What am I missing? Thanks
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import pandas as pd
import numpy as np
app = Dash(__name__)
df = px.data.gapminder()
list_of_continents = ["Asia", "Africa", "Europe", 'Oceania', 'Americas']
app.layout = html.Div([
html.H4('Restaurant tips by day of week'),
dcc.Dropdown(
id="dropdown",
options=list_of_continents,
multi=False
),
dcc.Graph(id="graph"),
#dcc.Graph(id ='graph2') ##????
])
#app.callback(
Output("graph", "figure"),
Input("dropdown", "value")
)
def make_scatter_plot( value =[i for i in list_of_continents], df = df):
"""
"""
data = df[df['continent'].isin([value])]
fig = px.scatter(data, x="lifeExp", y="gdpPercap",
size="pop")
return fig
if __name__ == '__main__':
app.run_server(debug=True)
although plotly express can help you set up a graph with just one line of code it’s not very handy when it comes to customizing each trace. So, for that, you’ve to switch to graphs_objects.
In the following lines of code, the callback generates a Graph component for each trace and appends each graph component to a Div component. Hence you get multiple graphs using a single callback.
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
app = Dash(__name__)
df = px.data.gapminder()
app.layout = html.Div([
html.H4('Restaurant tips by day of week'),
html.Div(id='graphs',children=[])
])
#app.callback(
Output("graphs", "children"),
Input("graphs", "children")
)
def make_scatter_plot(child):
"""
"""
for continent in df['continent'].unique():
df_filtered = df[df['continent'] == continent]
fig = go.Figure()
fig.add_trace(
go.Scatter(x = df_filtered['lifeExp'],
y = df_filtered['gdpPercap'],
mode = 'markers',
marker = dict(size = 10 + (df_filtered['pop'] - df_filtered['pop'].min()) * 20
/ (df_filtered['pop'].max() - df_filtered['pop'].min())) # This is just to scale the marker size value between 10 and 20.
)
)
fig.update_layout(
title_text = continent
)
child.append(dcc.Graph(figure=fig))
return child
if __name__ == '__main__':
app.run_server(debug=True)
The output of the Code:
Click here
I am new to plotly. I want to draw some bounding boxes interactively on an image and get their coordinates to a list (top-left, bottom-right corners). This should be done in google colab, so CV2 did not work. This link gives an example of interactively select (drag and draw) an area of an image while this link can be used to extract coordinates on a plot using plotly. I still could not figure out how to combine these 2 examples together and return the bounding box coordinates. I have added these code snippets below.
To draw the bounding boxes on image:-
!wget https://gamingnewsanalyst.com/wp-content/uploads/2020/03/Crysis-3-Free-Download-800x450.jpg
import plotly.express as px
import cv2
img = cv2.cvtColor(cv2.imread('/content/Crysis-3-Free-Download-800x450.jpg'),cv2.COLOR_BGR2RGB)
fig = px.imshow(img)
fig.update_layout(
dragmode='drawrect',
newshape=dict(line_color='cyan'))
fig.show()
example image
To get the coordinates of mouse click point:-
import plotly.graph_objects as go
from google.colab import output
output.enable_custom_widget_manager()
import numpy as np
np.random.seed(1)
x = np.random.rand(100)
y = np.random.rand(100)
f = go.FigureWidget([go.Scatter(x=x, y=y, mode='markers')])
scatter = f.data[0]
colors = ['#a3a7e4'] * 100
scatter.marker.color = colors
scatter.marker.size = [10] * 100
f.layout.hovermode = 'closest'
# create our callback function
def update_point(trace, points, selector):
c = list(scatter.marker.color)
s = list(scatter.marker.size)
for i in points.point_inds:
c[i] = '#bae2be'
s[i] = 20
print(points)
with f.batch_update():
scatter.marker.color = c
scatter.marker.size = s
scatter.on_click(update_point)
f
When I click on a point this code will given below output
Points(point_inds=[6],
xs=[0.1862602113776709],
ys=[0.015821242846556283],
trace_name='trace 0',
trace_index=0)
My expected output should be the drawn bounding boxes' coordinates.
[[100,100],[500,400]] ## [[x0,y0],[x1,y1]]
[[200,130],[400,300]]
Any help is much appreciated.
Thanks in advance.
After some search found this example which tells how to do this. I modified it and partially inserted inside a class as follows to satisfy my full requirement.
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from jupyter_dash import JupyterDash
import cv2
import numpy as np
coords = []
depths = []
app = JupyterDash(__name__)
#app.callback(
Output("bbox-data", "children"),
Input("middle-sai", "relayoutData"),
prevent_initial_call=True,
)
def on_new_annotation(relayout_data):
global coords,depths
if "shapes" in relayout_data:
x0 = int(relayout_data["shapes"][-1]['x0'])
y0 = int(relayout_data["shapes"][-1]['y0'])
x1 = int(relayout_data["shapes"][-1]['x1'])
y1 = int(relayout_data["shapes"][-1]['y1'])
coords_dict = {'x0':x0,'y0':y0,'x1':x1,'y1':y1}
coords.append([[y0,x0],[y1,x1]])
depths.append(None)
return html.Div([html.Div(str(coords_dict)),html.Div(dcc.RadioItems(['shallow', 'deep'],inline=True,id = "depth-sel"))],style={"color": "white"})
else:
return dash.no_update
#app.callback(
Output('all-bboxes','children'),
Input("depth-sel","value"),
)
def display_final_list(depth_val):
global coords,depths
if (depth_val == 'deep'):
depths[-1] = 1
elif (depth_val == 'shallow'):
depths[-1] = 0
flist = ''
for i in range(len(coords)):
flist += str(coords[i])
if (depths[i]==1):
flist += " deep "
elif (depths[i]== 0):
flist += " shallow "
else:
flist += " " + str(depths[i]) + " "
return flist
class test_show():
def __init__(self):
global coords,depths
coords = []
depths = []
self.img = cv2.cvtColor(cv2.imread('/content/Crysis-3-Free-Download-800x450.jpg'),cv2.COLOR_BGR2RGB)
fig = px.imshow(self.img)
fig.update_layout(
autosize=False,
# width=500,
height=400,
dragmode='drawrect',
newshape=dict(line_color='cyan'))
app.layout = html.Div(
[
dcc.Graph(id="middle-sai", figure=fig),
html.Pre(id="bbox-data",style={"color": "white"}),
html.Div(id='all-bboxes',style={"color": "white"}),
]
)
def show_image(self):
app.run_server(debug=False,mode='inline')
t = test_show()
t.show_image()
I am trying to use and adjust the rangeslider property in the update_layout of a Python Dash core component Graph. I like to rebase data to a relative performance chart whenever the rangeslider is changed such that the performance is always measured to the first element in the selected range. I was able to accomplish this by using a RangeSlider as an input for the Graph component. However, I was wondering if this is also possible only using the rangeslider property in the Graph component directly without the callback.
I am looking for something similar to this solution in R: https://mgei.github.io/post/rangeslider-plotly/.
# Libraries
import pandas as pd
import numpy as np
import datetime
from dash import Dash, html
import dash_core_components as dcc
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
import plotly.express as px
# Data
T = 100
steps = 10
base = datetime.datetime.today()
date_list = reversed([base - datetime.timedelta(days=x) for x in range(T)])
test_data = pd.DataFrame(np.random.randn(T)/100, index=date_list, columns=['Col1'])
test_data.iloc[0,:] = 0
# App
app = Dash(__name__)
app.layout = html.Div([html.H3('RangeSlider'),
dcc.RangeSlider(0, T, steps, count=1,
marks={i:test_data.index[i].strftime('%d.%m.%y') for i in range(0,T,steps)},
id='range_slider'),
html.Br(),
html.H3('Plot'),
dcc.Graph(figure={'data':[]}, id='plot_data'),
],
style={'width': '50%', 'display': 'inline-block', 'padding-left':'25%', 'padding-right':'25%'}
)
# Callbacks
#app.callback(Output('plot_data', 'figure'),
Input('range_slider', 'value'))
def plot_data(value):
if value is None:
raise PreventUpdate
else:
tmp_data = (1+test_data.iloc[value[0]:value[1],:]).cumprod() * 100
tmp_data.iloc[0,:] = 100
tmp_data = tmp_data.sort_index()
fig = px.line(tmp_data, y=['Col1'])
fig.update_layout(xaxis=dict(rangeslider=dict(visible=True), type='date'))
fig.update_layout(showlegend=True)
return fig
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False)
The reason why it did not work is probably due to the fact that the time series data of the test data does not match the time series data of the slider.
My fixes included changing the date in datetime.date.today() to a date, changing the date format of the slider to a hyphenated format (unrelated to the failure), expanding the slider to 75% of the browser width because it was too short, and adjusting the margins.
# Libraries
import pandas as pd
import numpy as np
import datetime
from dash import Dash, html, dcc
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
from jupyter_dash import JupyterDash
import plotly.express as px
# Data
T = 100
steps = 10
base = datetime.date.today()
date_list = reversed([base - datetime.timedelta(days=x) for x in range(T)])
test_data = pd.DataFrame(np.random.randn(T)/100, index=date_list, columns=['Col1'])
test_data.iloc[0,:] = 0
#fig = px.line(test_data, y=['Col1'])
# App
#app = Dash(__name__)
app = JupyterDash(__name__)
app.layout = html.Div([html.H3('RangeSlider'),
dcc.RangeSlider(0, T, steps, count=1,
marks={i:test_data.index[i].strftime('%d-%m-%y') for i in range(0,T,steps)},
id='range_slider'),
html.Br(),
html.H3('Plot'),
dcc.Graph(figure={'data':[]}, id='plot_data'),
],
style={'width': '75%', 'display': 'inline-block', 'padding-left':'10%', 'padding-right':'15%'}
)
# Callbacks
#app.callback(Output('plot_data', 'figure'),
Input('range_slider', 'value'))
def plot_data(value):
if value is None:
raise PreventUpdate
else:
tmp_data = (1+test_data.iloc[value[0]:value[1],:]).cumprod() * 100
tmp_data.iloc[0,:] = 100
tmp_data = tmp_data.sort_index()
print(tmp_data)
fig = px.line(tmp_data, y=['Col1'])
fig.update_layout(xaxis=dict(rangeslider=dict(visible=True), type='date'))
fig.update_layout(showlegend=True)
return fig
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False, mode='inline')
I have this interactive plot in python:
import ipywidgets as widgets
import plotly.graph_objects as go
from numpy import linspace
def leaf_plot(sense, spec):
fig = go.Figure()
x = linspace(0,1,101)
x[0] += 1e-16
x[-1] -= 1e-16
positive = sense*x/(sense*x + (1-spec)*(1-x))
#probability a person is infected, given a positive test result,
#P(p|pr) = P(pr|p)*P(p)/P(pr)
# = P(pr|p)*P(p)/(P(pr|p)*P(p) + P(pr|n)*P(n))
# = sense*P(p)/( sense*P(p) +(1-spec)*P(n))
negative = 1-spec*(1-x)/((1-sense)*x + spec*(1-x))
fig.add_trace(
go.Scatter(x=x, y = positive, name="Positive",marker=dict( color='red'))
)
fig.add_trace(
go.Scatter(x=x, y = negative,
name="Negative",
mode = 'lines+markers',
marker=dict( color='green'))
)
fig.update_xaxes(title_text = "Base Rate")
fig.update_yaxes(title_text = "Post-test Probability")
fig.show()
sense_ = widgets.FloatSlider(
value=0.5,
min=0,
max=1.0,
step=0.01,
description='Sensitivity:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='.2f',
)
spec_ = widgets.FloatSlider(
value=0.5,
min=0,
max=1.0,
step=0.01,
description='Specificity:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='.2f',
)
ui = widgets.VBox([sense_, spec_])
out = widgets.interactive_output(leaf_plot, {'sense': sense_, 'spec': spec_})
display(ui, out)
How can I export this so that it can be viewed as a standalone web page in a browser, say as HTML, while retaining the interactivity, as e.g. in https://gabgoh.github.io/COVID/index.html ?
Using plotly's fig.write_html() option I get a standalone web page, but this way I lose the sliders.
With some modification, plotly allows at most for a single slider (the ipywidgets are not included in the plotly figure object).
Plus, in plotly, the said slider basically controls the visibility of pre-calculated traces (see e.g. https://plotly.com/python/sliders/), which restricts the interactivity (sometimes the parameter space is huge).
What's the best way to go?
(I don't necessarily need to stick with plotly/ipywidgets)
you need to rework things a bit, but you can achieve what you want with dash and Heroku.
first you need to modify leaf_plot() to return a figure object.
from numpy import linspace
def leaf_plot(sense, spec):
fig = go.Figure()
x = linspace(0,1,101)
x[0] += 1e-16
x[-1] -= 1e-16
positive = sense*x/(sense*x + (1-spec)*(1-x))
negative = 1-spec*(1-x)/((1-sense)*x + spec*(1-x))
fig.add_trace(
go.Scatter(x=x, y = positive, name="Positive",marker=dict( color='red'))
)
fig.add_trace(
go.Scatter(x=x, y = negative,
name="Negative",
mode = 'lines+markers',
marker=dict( color='green'))
)
fig.update_layout(
xaxis_title="Base rate",
yaxis_title="After-test probability",
)
return fig
Then write the dash app:
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
# Build App
app = JupyterDash(__name__)
app.layout = html.Div([
html.H1("Interpreting Test Results"),
dcc.Graph(id='graph'),
html.Label([
"sensitivity",
dcc.Slider(
id='sensitivity-slider',
min=0,
max=1,
step=0.01,
value=0.5,
marks = {i: '{:5.2f}'.format(i) for i in linspace(1e-16,1-1e-16,11)}
),
]),
html.Label([
"specificity",
dcc.Slider(
id='specificity-slider',
min=0,
max=1,
step=0.01,
value=0.5,
marks = {i: '{:5.2f}'.format(i) for i in linspace(1e-16,1-1e-16,11)}
),
]),
])
# Define callback to update graph
#app.callback(
Output('graph', 'figure'),
Input("sensitivity-slider", "value"),
Input("specificity-slider", "value")
)
def update_figure(sense, spec):
return leaf_plot(sense, spec)
# Run app and display result inline in the notebook
app.run_server()
If you execute this in a jupyter notebook, you will only be able to access your app locally.
If you want to publish, you can try Heroku
With plotly, after creating the figure, save it:
fig.write_html("path/to/file.html")
Also try this parameter in the function:
animation_opts: dict or None (default None)
dict of custom animation parameters to be passed to the function
Plotly.animate in Plotly.js. See
https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js
for available options. Has no effect if the figure does not contain
frames, or auto_play is False.
Otherwise, check here for some suggestions: https://community.plotly.com/t/export-plotly-and-ipywidgets-as-an-html-file/18579