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

Animation Produces extremely large files

I am trying to animate simple particle trajectories. I have 2 particles currently. Below is a stripped down example of what I am trying to do. At even 2000 frames, the output html is 91 MB and that appears to be scale about n^1.5. For ~2x3x2000 data points I would expect that data itself to take ~100 kB. We appear to be taking 1000 times more than that for the plot.

I was able to turn relayout off and and that cut things down by a factor of 5 or so. Any other suggestions for performance would be appreciated. It would also be a useful thing to add to the docs.
Thank you for your help.

from plotly.offline import init_notebook_mode, plot
import numpy as np
init_notebook_mode()

t = np.arange(2000)
x = np.cos(2.0 * np.pi /365.0 * t)
y = np.sin(2.0 * np.pi /365.0 * t)
z = np.zeros(len(t))

figure = {'data': [{'x': [0, 1], 'y': [0, 0], 'z': [0, 0], 'type': 'scatter3d', 'mode': 'markers', 
                    'marker': {'color': ['#ff7f0e', '#1f77b4'], 'size': [100.0, 20.0]}}, 
                   {'x': [0, 1], 'y': [0, 0], 'z': [0, 0], 'type': 'scatter3d', 'mode': 'lines', 
                    'line': {'color': '#1f77b4', 'width': 2.0}}],
          'layout': {'autosize': False, 'width': 1000, 'height': 1000, 'showactive': False,
                     'scene': {'xaxis': {'range': [-1.5, 1.5], 'autorange': False, 'dtick':1},
                               'yaxis': {'range': [-1.5, 1.5], 'autorange': False, 'dtick':1},
                               'zaxis': {'range': [-2, 2], 'autorange': False, 'dtick':1},
                                'aspectratio': dict( x=1, y=1, z=1),
                                'aspectmode': 'manual',},

                     'updatemenus': [{'type': 'buttons',
                                      'buttons': [{'args': [None, 
                                                            {'frame': {'duration': 30, 'redraw': False, 
                                                                       'relayout': False, 'restyle': False},
                                                             'fromcurrent': True, 'mode': 'immediate',
                                                             'transition': {'duration': 30}
                                                            }],
                                                   'label': 'Play',
                                                   'method': 'animate'},
                                                  {'args': [[None], {'frame': {'duration': 0, 'redraw': False, 
                                                                               'relayout': False, 'restyle': False},
                                                                     'mode': 'immediate', 'transition': {'duration': 0}}],
                                                   'label': 'Pause',
                                                   'method': 'animate'
                                                  }]}]},
          'frames': [{'data': [{'x': [0.0, x[i]],
                                'y': [0.0, y[i]],
                                'z': [0.0, z[i]], 'type': 'scatter3d'}, 
                               {'x': x[:i],
                                'y': y[:i],
                                'z': z[:i], 'type': 'scatter3d'}]} 
                     for i in t],

         }

plot(figure, validate=False, filename='test_plot.html',) ```

Hi @cmccully,

I did two quick experiments:

First, I wrote your figure dict out to a JSON file…

import json
from plotly.utils import PlotlyJSONEncoder
with open('animation.json', 'w') as f:
    json.dump(figure, f, cls=PlotlyJSONEncoder)

which resulted in a 92.6MB json file.

Then I wrote the dict to a pickle file…

import pickle
with open('animation.pickle', 'wb') as f:
    pickle.dump(figure, f)

which resulted in a 48.7 MB file. This factor of ~2 is about what you would expect given that the pickle file can store the numpy arrays as binary buffers and the JSON file stores them as lists in string form.

I haven’t looked at the details of what’s in figure, could you elaborate on why you would expect the data to take ~100kB?

-Jon

Sorry for the delay. I didn’t see the response until now.

For 2x3x2000 data points, that gives 12,000 total data points that you need to store. Assuming they are 8 byte floats (as they would be in the pickle file you mention), that 8 bytes * 12000 = 100 kB for the raw data. I understand that there is going to be some overhead, but a factor of 500 seemed a little excessive.

My overall goal is to make an animation of a very simple N-body simulator, but currently the animations I produce are just too large to be practical.

I guess the real question I have is whether you can have a list of x,y,z coordinates as a function of ~10000 time steps that you can then animate in a 3d graph. The issue may be that I am fundamentally approaching the problem in a way that is not how plotly works.

As I was working with this, I realized the root of my problem. I am repeating the points for each line for each time step. That is leading to a factorial expansion of the number of points that get saved to the dict. Is there a way in plotly to only save the points once, and then have plotly render a range of points without having to directly repeat them in the frames dict?

Hi @cmccully,

Yeah, this is the trouble you’re running into. You might be able to do this using a filter transform (https://plot.ly/python/filter/). In this case you would store the full data set in the data portion of your figure. And your animate frames would update the filter’s value.

transforms aren’t supported by the objects in the plotly.graph_objs hierarchy, so you would need to build the figure using plain dict and list objects and then set validate=False when saving it to html with plotly.offline.plot (and it looks like you’re already doing this).

-Jon

Thanks for this!
Is there an example somewhere that I could look at updating filters in animated frames?

Hi @cmccully,

I couldn’t find one, so here’s a simple one you can start with

from plotly.offline import plot

fig = {
    'data': [{
        'type': 'scatter',
        'mode': 'lines',
        'x': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        'y': [0, 2, 2, 3, 2, 4, 1, 4, 3, 6],
        'transforms': [{
            'type': 'filter',
            'target': 'x',
            'operation': '<=',
            'value': 0
        }]
    }],
    'layout': {
        'xaxis': {'range': [0, 10]},
        'yaxis': {'range': [0, 10]}
    },
    'frames': [{
        'data': [{
            'transforms': [{
                'value': i
            }]
        }]
    } for i in range(10)]
}

plot(fig, validate=False)

filter_animate

Hope that helps!
-Jon

2 Likes

Fabulous! Thank you so much! In fact I think you should add this to the examples section on the plotly site. This is so helpful.

1 Like