Black Lives Matter. Please consider donating to Black Girls Code today.

Scatter Plot Animation - Trouble Adding Trailing Lines

Hi - I am hoping to create an animated scatterplot that plots two continuous variables over time, and uses a slider-animation similar to what is found in the tutorial at the bottom of this link to visualize the points moving across time. Link below:

The one addition that I am hoping to add is to include trailing lines for each datapoint as the animation progresses to better show the progress of each line. A good example of the trailing line can be found in this chart:

I thought that this could be accomplished by simply changing “mode”: “markers”, to “mode”: “lines+markers”, but this did not work and returns the same graph. Do you know the next best steps in order to change our script so that we can achieve the trailing line effect? Do we need to change our layout from filling in python dictionaries, to using the go.layout, go.scatter, and go.frame format?

Any help would be massively appreciated! Script for plotting the graph without generating trailing lines is pasted below.

make figure

figure = {
‘data’: ,
‘layout’: {},
‘frames’:
}
config = {‘scrollzoom’: True}

fill in most of layout

figure[‘layout’][‘title’] = {‘text’:‘Daily Frequency of Key Covid-19 Hashtags plotted against cumulative total of tweets per hashtag’}
figure[‘layout’][‘xaxis’] = {‘title’: ‘total tweets’, ‘type’: ‘log’, ‘range’: [0,5]}
figure[‘layout’][‘yaxis’] = {‘title’: ‘word count’,‘type’: ‘log’, ‘range’: [0,3]}
figure[‘layout’][‘hovermode’] = ‘closest’
figure[‘layout’][‘sliders’] = {
‘args’: [
‘slider.value’, {
‘duration’: 400,
‘ease’: ‘cubic-in-out’
}
],
‘initialValue’: ‘20191001’,
‘plotlycommand’: ‘animate’,
‘values’: years,
‘visible’: True
}

figure[‘layout’][‘updatemenus’] = [
{
‘buttons’: [
{
‘args’: [None, {‘frame’: {‘duration’: 500, ‘redraw’: False},
‘fromcurrent’: True, ‘transition’: {‘duration’: 300, ‘easing’: ‘quadratic-in-out’}}],
‘label’: ‘Play’,
‘method’: ‘animate’
},
{
‘args’: [[None], {‘frame’: {‘duration’: 0, ‘redraw’: False}, ‘mode’: ‘immediate’,
‘transition’: {‘duration’: 0}}],
‘label’: ‘Pause’,
‘method’: ‘animate’
}
],
‘direction’: ‘left’,
‘pad’: {‘r’: 10, ‘t’: 87},
‘showactive’: False,
‘type’: ‘buttons’,
‘x’: 0.1,
‘xanchor’: ‘right’,
‘y’: 0,
‘yanchor’: ‘top’
}
]

sliders_dict = {
‘active’: 0,
‘yanchor’: ‘top’,
‘xanchor’: ‘left’,
‘currentvalue’: {
‘font’: {‘size’: 20},
‘prefix’: ‘Year:’,
‘visible’: True,
‘xanchor’: ‘right’
},
‘transition’: {‘duration’: 300, ‘easing’: ‘cubic-in-out’},
‘pad’: {‘b’: 10, ‘t’: 50},
‘len’: 0.9,
‘x’: 0.1,
‘y’: 0,
‘steps’:
}

size = [1]

make data

year = 20200110
for word in words:
dataset_by_year = dataset[dataset[‘datestring’] == year]
dataset_by_year_and_word=dataset_by_year[dataset_by_year[‘word’] == word]

data_dict = {
    'x': list(dataset_by_year_and_word['word_cumu_sum']),
    'y': list(dataset_by_year_and_word['word_count']),
    'mode': 'markers',
    #'text': list(dataset_by_year_and_gov['cases']),
    'marker': {
         #   'sizemode': 'area',
          #  'sizeref': 2.*max(size)/(40.**.5),
            'size':20
        },
    'name': word,
    'type': 'scatter',
    'showlegend': True
}
figure['data'].append(data_dict)

make frames

for year in years:
frame = {‘data’: , ‘name’: str(year)}
for word in words:
dataset_by_year = dataset[dataset[‘datestring’] == int(year)]

    dataset_by_year_and_word=dataset_by_year[dataset_by_year['word'] == word]

    data_dict = {
        'x': list(dataset_by_year_and_word['word_cumu_sum']),
        'y': list(dataset_by_year_and_word['word_count']),
        'mode': 'markers',
        #'text': list(dataset_by_year_and_gov['cases']),
        'marker': {
         #   'sizemode': 'area',
          #  'sizeref': 2.*max(size)/(40.**.5),
            'size':20
        },
        'name': word,
        'type': 'scatter',
        'showlegend': True
    }
    frame['data'].append(data_dict)

figure['frames'].append(frame)
slider_step = {'args': [
    [year],
    {'frame': {'duration': 300, 'redraw': False},
     'mode': 'immediate',
   'transition': {'duration': 300}}
 ],
 'label': year,
 'method': 'animate'}
sliders_dict['steps'].append(slider_step)

from plotly.offline import init_notebook_mode, iplot
from IPython.display import display, HTML

import pandas as pd

init_notebook_mode(connected=True)
figure[‘layout’][‘sliders’] = [sliders_dict]

iplot(figure, config=config)

2 Likes

I’m having a similar problem when trying to connect the markers with a line in an animation frame

I faced a similar issue. I couldn’t find any example on Plotly’s documentation with Python where I can display the data based on the position of slider. go.Scatter method connects all the data points in the graph but I just want it to connect only those data points where the slider is currently placed!

Hi @chins,
Welcome to forum.
To animate the trendline of two scatter traces, mode='lines' you should define initial data and the frames as follows:

n_frames=30
y1 = #an array of shape (30,) or a dataframe column
y2 =#an array of shape(30,)
fig = go.Figure(data=[go.Scatter(x=[0], y=[y1[0]], mode='lines', line_color='blue'),
                               go.Scatter(x=[0], y=[y2[0]], mode='lines', line_color='red')])


frames = [go.Frame(data= [go.Scatter( #update trace0, i.e. fig.data[0]
                               x=np.arange(k+1),
                               y=y1[:k+1]),
                          go.Scatter(#updates trace1, i.e. fig.data[1]
                               x=np.arange(k+1),
                               y=y2[:k+1])],
                   traces=[0,1]# this line informs plotly.js that the first Scatter updates trace0, and the second, trace1
                   name=f'frame{k}'       
              )for k  in  range(1,n_traces)]   

A concrete example I posted here a while ago, as an answer to a similar question: https://community.plotly.com/t/cumulative-lines-animation-in-python/25707/2

3 Likes

Thanks so much @empet for the example code. I was exactly looking for this as the go.Scatter() method plots everything by default. Means a lot!

Thanks @empet for the response! This is super helpful. Do you by any chance have sample code that incorporates a slider into the visualization? We are hoping to plot two continuous/numeric variables on the X and Y, and then have a slider that can show the different X and Y values across different dates.

As per the link above, x = df.Date[:2], the x axis is measured by date. Do we have to reference a date value in the initial x and y values in order to link them up to a slider-by-date further on in the analysis?

Thanks so much again!

Matt

@mvitha

To associate a slider to this animation https://community.plotly.com/t/cumulative-lines-animation-in-python/25707/2 you must
give a name to each frame, because the slider is connected to the frames, via their names.
Hence the frame definition will be:

frames = [dict(data= [dict(type='scatter',
                           x=df.Date[:k+1],
                           y=low[:k+1]),
                      dict(type='scatter',
                           x=df.Date[:k+1],
                           y=high[:k+1])],
               traces= [0, 1], 
               name = f'frame{k+1}'
              )for k  in  range(1, len(low)-1)] 

Then define the slider as follow:

sliders = [dict(steps = [dict(method= 'animate',
                              args= [[f'frame{k+1}'], #HERE IS THE k^th FRAME NAME                          
                              dict(mode= 'immediate',
                                   frame= dict(duration=3, redraw= False),
                                   transition=dict(duration= 0))
                                 ],
                              label=f'{k+1}'  #label for each frame marked on the slider
                             ) for k in range(1, len(low)-1)], 
                active=1,
                transition= dict(duration= 0 ),
                x=0, # slider starting position  
                y=0, 
                currentvalue=dict(font=dict(size=12), 
                                  prefix='frame: ', 
                                  visible=True, 
                                  xanchor= 'center'
                                 ),  
               len=1.0) #slider length
           ]

fig.update_layout(sliders=sliders)