Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Description
Problem
I'm thrilled that SVG animations will be fully supported in the upcoming release. I've been doing some testing to see how it compares to other animation formats, and to my surprise, the resulting file size of an jshtml svg animation is huge! Here's a small test program that compares the filesize of animations:
importsysimportnumpyasnpfromtqdm.autoimporttqdm,trangeimportmatplotlib.pyplotaspltimportmatplotlib.animationasanimationfromIPython.displayimportHTMLplt.rcParams['animation.embed_limit']=2**128defanim_memfootprint(size,frame_format='png'):plt.rcParams['animation.frame_format']=frame_formatdefupdate_line(num,data,line):line.set_data(data[..., :num])returnline,fig1=plt.figure()# Fixing random state for reproducibilitynp.random.seed(19680801)data=np.random.rand(2,size)l,=plt.plot([], [],'r-')plt.xlim(0,1)plt.ylim(0,1)line_ani=animation.FuncAnimation(fig1,update_line,size,fargs=(data,l),interval=50,blit=True)footprint=sys.getsizeof(line_ani.to_jshtml())plt.close(fig1)returnfootprintx=list(range(110,5100,100))pngs= [anim_memfootprint(i,'png')foriintqdm(x)]svgs= [anim_memfootprint(i,'svg')foriintqdm(x)]jpegs= [anim_memfootprint(i,'jpeg')foriintqdm(x)]plt.plot(x,pngs,label='pngs')plt.plot(x,svgs,label='svgs')plt.plot(x,jpegs,label='jpegs')plt.legend()
If we plot this we get the following plot, with #points/frames on the X-axis and #bytes of the resulting animation on the Y-axis:
The Y-axis is in 100s of MBs! Sure, there are up to 5000 frames and a max of 5000 points to plot, but still!
If we consider that each point is an (x, y) pair, each 32bits then that's 5000x2x(32/8) = 40,000 bytes or about 0.04 MB of data to plot. Here, all the rest is static from frame to frame and therefore should be a constant overhead.
Proposed Solution
In these animations, each frame is stored separately and displayed as an image using an image tag with a data URL (where the image is passed in as a base64 encoded string). This makes sense if the format is something like PNG or JPEG as we can't easily only redraw part of it, but for SVG we totally can!
In fact, manipulating SVG via JS is very common. We could have a single SVG image and use JS to only show the parts that are relevant for each frame. In the case of the above, we could do this via thedashoffset
SVG property as showcasedhere.
Blitting this way is not always possible as animations can do more than animate paths. But there are other SVG attributes that can be set dynamically that can be used to not have to redraw the whole frame every time. For instance, the SVGvisibility
attribute (seehere) can be set as needed. This would enable SVG animations to only contain one "master" SVG document that is edited as needed.
This is not necessarily complicated either, say there is very little overlap between frames because the animation shows a series of images. We'd have something like:
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg"><!-- A ton of metadata goes here--><!-- Setup axes, title, legends, etc here--> <imageid="frame-1"href="..."height="200"width="200"/> <imageid="frame-2"href="..."height="200"width="200"/> <imageid="frame-3"href="..."height="200"width="200"/> <imageid="frame-4"href="..."height="200"width="200"/><!-- etc--></svg>
Then the JS which is responsible for currently updating that image tag I mentioned previously can simply set the visibility of the current frame tovisible
and everything else tohidden
. This would enable re-use of the rest of the figure, and allow for a huge reduction in filesize.
I'm sure there are other tricks we can play here, but the point is that with SVG animations we can blit and it'll make everything so much faster. I'm routinely generating animations that are 200MB which could be only a few MBs.
And if we can't effectively perform this blitting, then we can simply fall back on the current behavior!