Hey Dash Community ā
Weāre actively exploring bringing ātransitionsā to dcc.Graph. This will allow your charts to update from one state to the next smoothly, as if it were animated.
Weād love your feedback on the feature. Hereās how it works:
pip install dash-core-components==0.39.0rc3
The layout
object of the figure
property of dcc.Graph
now contains a transition
attribute. If supplied, then the chart will smoothly transition on update. transition
also describes the parameters of the transition like how long it should take and how the āeasingā should work.
Hereās a quick example:
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
app = dash.Dash(__name__)
app.scripts.config.serve_locally = True
app.css.config.serve_locally = True
app.layout = html.Div([
dcc.Dropdown(
id='color',
options=[i for i in [
{'label': 'Navy', 'value': '#001f3f'},
{'label': 'Blue', 'value': '#0074D9'},
{'label': 'Aqua', 'value': '#7FDBFF'},
{'label': 'TEAL', 'value': '#39CCCC'},
{'label': 'OLIVE', 'value': '#3D9970'},
{'label': 'GREEN', 'value': '#2ECC40'}
]],
value='#2ECC40'
),
dcc.Dropdown(
id='size',
options=[{'label': str(i), 'value': str(i)} for i in range(5, 50)],
value='20'
),
dcc.Graph(id='graph'),
])
@app.callback(Output('graph', 'figure'), [
Input('color', 'value'),
Input('size', 'value'),
])
def update_graph(color, size):
return {
'data': [{
'x': [
1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
],
'y': [
219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
350, 430, 474, 526, 488, 537, 500, 439
],
'type': 'scatter',
'mode': 'markers',
'marker': {
'color': color,
'size': int(size)
}
}],
'layout': {
# You need to supply the axes ranges for smooth animations
'xaxis': {
'range': [1993, 2013]
},
'yaxis': {
'range': [75, 570]
},
'transition': {
'duration': 500,
'easing': 'cubic-in-out'
}
}
}
if __name__ == '__main__':
app.run_server(debug=True)
There are a few important limitations to be aware of:
-
Fixed Axes Ranges. You need to set the
range
inlayout
manually. These types of transitions will animate the properties indata
separately from the properties inlayout
(this is more for technical reasons rather than by design). So, if you donāt specify therange
manually, the graph will recompute the new range, then animate the changing the axes first, then animate the points. The result would feel a little janky:
However, if you fix the ranges, then the points can animate without causing any ālayoutā updates:
-
Warm Up Animation. There is an animation artifact on first draw. This is just a bug and weāll fix it before we release.
-
Transitions are only smooth with with
'type': 'scatter'
You can keeptransition
inlayout
for all of your chart types, but itāll only actually create a smooth transition when'type': 'scatter'
. Note that this works for line charts as well, since line charts are described as'type': 'scatter', 'mode': 'lines'
.
Hereās the code for the scatter plot example above:
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import numpy as np
import pandas as pd
import plotly.graph_objs as go
df = pd.read_csv(
'https://raw.githubusercontent.com/plotly/'
'datasets/master/gapminderDataFiveYear.csv')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Graph(id='graph-with-slider'),
dcc.Slider(
id='year-slider',
min=df['year'].min(),
max=df['year'].max(),
value=df['year'].min(),
marks={str(year): str(year) for year in df['year'].unique()}
)
])
@app.callback(
dash.dependencies.Output('graph-with-slider', 'figure'),
[dash.dependencies.Input('year-slider', 'value')])
def update_figure(selected_year):
filtered_df = df[df.year == selected_year]
traces = []
for i in filtered_df.continent.unique():
df_by_continent = filtered_df[filtered_df['continent'] == i]
traces.append(go.Scatter(
x=df_by_continent['gdpPercap'],
y=df_by_continent['lifeExp'],
text=df_by_continent['country'],
mode='markers',
opacity=0.7,
marker={
'size': 15,
'line': {'width': 0.5, 'color': 'white'}
},
name=i
))
return {
'data': traces,
'layout': dict(
xaxis={
'type': 'log',
'title': 'GDP Per Capita',
'range': [
# an idiosyncracy of plotly.js graphs: with log axes,
# you need to take the log of the ranges that you want
np.log10(df['gdpPercap'].min()),
np.log10(df['gdpPercap'].max()),
]
},
yaxis={
'title': 'Life Expectancy',
'range': [20, 90]
},
margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
legend={'x': 0, 'y': 1},
hovermode='closest',
transition={
'duration': 500,
'easing': 'cubic-in-out'
}
)
}
if __name__ == '__main__':
app.run_server(debug=True)
Thanks for checking this out and let us know what you think!