📣 Exploring a "Transitions" API for dcc.Graph


#1

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


Animation options in Dash
My callback is updating only the lines of the graph but not the legend
Separate Animations and Slider Data in One Plots
#2

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


#3

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


#4

It is:
image


#5

I’ve just checked it and have the same problem.


#6

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.


#7

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?


#8

It will not affect the performance.


#9

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.


#10

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


:mega: Dash Club - Dispatch #4 - Latest Dash Developments
#11

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


#12

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


#13

Right now only scatter traces are supported. Sankey diagrams have their own built-in transitions as well. We’re tracking this in https://github.com/plotly/plotly.js/issues/3456


#14

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


#15

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.


#16

You have to pass back the entire figure currently, it’s not possible to pass back ‘incremental updates’.