šŸ“£ Exploring a "Transitions" API for dcc.Graph

:wave: 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:

  1. Fixed Axes Ranges. You need to set the range in layout manually. These types of transitions will animate the properties in data separately from the properties in layout (this is more for technical reasons rather than by design). So, if you donā€™t specify the range 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:
  2. Warm Up Animation. There is an animation artifact on first draw. This is just a bug and weā€™ll fix it before we release.
  3. Transitions are only smooth with with 'type': 'scatter'
    You can keep transition in layout 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!

14 Likes

Hi @chriddyp,

I tried it as follows:
def update_graph(json_df, fy, department, cc_selection, n):

pivoted_df  = pd.read_json(json_df, orient= 'split')

data = [go.Bar(
            x=cc_selection,
            y=pivoted_df.loc[cc_selection, period],
            name='P' + str(period),
			opacity=.75
			) for period in pivoted_df.columns]

layout = go.Layout(
    title='Actual Expenses of {} for FY{}'.format(department.upper(), str(fy)[2:]),
	xaxis = dict(type = 'category',
		#range=[cc_selection.min(), cc_selection_max()] 
		#title='Cost Center/s'
		),
	yaxis = dict(
		#range=[0, 1000000]
		#title = 'Expenses'
		),
	bargroupgap = 0.1,
	bargap = 0.1,
    hovermode='closest',
	font=dict(family='Arial', color='#7f7f7f'),
	transition={'duration': 300, 'easing': 'cubic-in-out'},
	width=1050
	)

fig = {
    'data': data,
    'layout': layout}

return fig

But it says that transition is not a valid attribute:
image

Check your dash-core-components version. Should be: 0.39.0rc3. I think.

It is:
image

Iā€™ve just checked it and have the same problem.

Since this hasnā€™t been merged into a formal plotly.py release yet, these options arenā€™t available in the graph_objs. However, you can always replace graph_objs with dict, so just replace go.Layout with dict to try this out.

Thank you, this is noted. Will try it out.

One more question, would incorporating transitions in the graph affect the performance of the Dash app?

It will not affect the performance.

Itā€™s working now! It looks like a Power BI dashboard now. Hehe

The only issue Iā€™ve noticed so far is that the title attibute of the graph does not update unless thereā€™s a huge change in the graph.

For example, mine is an unstacked bar charts (one for each month), the title will only change when the number of bars change.

However, this is not a big deal since I can just get rid of the graph title all at once.

This is awesome. When do you think it will be officially released?

1 Like

This has been officially released! Update dash to the latest version.

Hello, this is indeed very helpful, thanks a lot!
I wanted to ask on what types of plots we could use transitions. Are they available to all kinds of plots and graph objects? At the moment Iā€™m mostly interested in ParallelCategories but I would like to be aware of any limitation.
Cheers

Right now only scatter traces are supported. Sankey diagrams have their own built-in transitions as well. Weā€™re tracking this in More transitions Ā· Issue #3456 Ā· plotly/plotly.js Ā· GitHub

Awesome, thanks!
One more question though. What about object constancy during transitions in Dash?

This looks great. When programming animations in Plotly using data frames and slider steps it was possible to change attributes of the traces such as marker size. Having looked around the forums it seems this canā€™t be done done in Dash right now: animations-in-dash. It would be good to verify this, so:

Using Dash can we update trace attributes, such as an array of marker sizes or colours, using callbacks?

I imagine others passing this way may have the same question. The existing solution seems to be to listen to the state of the whole graph, tweak the bits which are important, and then output the whole tweaked graphā€”and in most cases this is probably plenty fast enough.

You have to pass back the entire figure currently, itā€™s not possible to pass back ā€˜incremental updatesā€™.

A post was split to a new topic: Animating lines from left to right

Thank you. I tried it and it is pretty useful. I have a questions about introduction to animation. at the end of gdp per capital chart, https://plot.ly/python/v3/animations/, the code is working for animation. I want to use the slider on the bottom as a callback for other figures as well. But i am not sure how to do that, as it is different from other sliders. Can you please enlighten me?

Hello. Iā€™m new to the community, so I apologize if itā€™s weird to reply to an old thread like this.
Iā€™d like to use a pie chart in dash with smooth transitions as it is updated. I see things like this are possible with React and D3 (Silky smooth Piechart transitions with React and D3.js | React for Data Visualization), but Iā€™m pretty new to all of this and am only really starting to get comfortable with python. Thanks for any help!