Creating static PDF reports using Quarto, LaTeX, Word, etc. is a classic way to share results. But there is a simple way to create more dynamic reports that can be easily updated and shared.
Exporting notebooks as HTML from Jupyter notebooks or .qmd files
You can export Jupyter notebooks or Quarto markdown files as HTML documents. These HTML documents can be easily shared and viewed in any web browser. They can also include interactive elements such as plots, widgets, and links. Furthermore, we can embed media such as animations, gifs, videos and interactive plots in these files for the reader to explore!
The beauty with quarto is that you write your report once and can export it to multiple formats such as HTML, PDF, Word, etc. with a single command!
A practical example: Embedding animations in reports
Let’s see how to do this in practice. Take the example of creating an animation of a point moving in a circle. We create this animation in a python block in a Jupyter notebook or a .qmd file and quarto will take care of rendering it in the final report.
Here we will first create the animation as a gif file and then embed it in the report.
where \(t\) is the parameter representing the angle in radians, and \(r=1\) is the radius of the circle. We create a numpy array of \(N=100\) values of \(t\) evenly spaced between \(0\) and \(2\pi\). Then for every value of \(t\) a red point is drawn at the corresponding \((x,y)\) coordinates. Finally, we save the animation as a gif file using the PillowWriter from the matplotlib.animation module.
Code
import numpy as npimport matplotlib.pyplot as pltimport matplotlib.animation as animationfig, ax = plt.subplots()line, = ax.plot([], [], 'ro')ax.set_xlim(-1.5, 1.5)ax.set_ylim(-1.5, 1.5)t = np.linspace(0, 2*np.pi, 100)y = np.sin(t)x = np.cos(t)ax.plot(x, y, 'b-')ax.set_aspect('equal', 'box')def update(frame): x = np.cos(frame) y = np.sin(frame) line.set_data([x], [y])return line,ani = animation.FuncAnimation(fig, update, frames=t, blit=True)plt.close()ani.save('circle_animation.gif', writer='pillow')
In this code we are using the matplotlib.animation module to create an animation of a point moving in a circle. We save the animation as a gif file using the PillowWriter. To embed the gif in the report, we use the markdown syntax for images.

Circle Animation
Important
Saving animations as gif files may require installing additional libraries such as Pillow. Make sure to install any necessary dependencies to ensure proper functionality.
We can export this notebook as HTML, having it include all media such as images and animations, using the command:
quarto render RichMediaReports.ipynb --to html --embed-resources
You can also add a YAML header to the notebook or .qmd file to specify that resources should be embedded in the HTML output:
This sets the theme for both dark and light modes, making dark the default. It also enables embedding of resources and adds a table of contents to the HTML output.
Having this in the YAML header means you can simply run:
quarto render RichMediaReports.ipynb
Warning
Embedding resources such as gifs can significantly increase the size of the HTML file. Be cautious when embedding large media files, as it may affect loading times and performance.
Especially gif files, while simple, are notoriously inefficient in terms of file size. For larger animations or videos, consider using more efficient formats like MP4 or WebM and embedding them using HTML5 video tags.
Exporting to video
We can export the same animation to a video file format such as MP4. This is usuallly much more efficient than using gif files, especially for longer animations or higher quality videos.
Code
import numpy as npimport matplotlib.pyplot as pltimport matplotlib.animation as animationfig, ax = plt.subplots()line, = ax.plot([], [], 'ro')ax.set_xlim(-1.5, 1.5)ax.set_ylim(-1.5, 1.5)t = np.linspace(0, 2*np.pi, 360*2)y = np.sin(t)x = np.cos(t)ax.plot(x, y, 'b-')ax.set_aspect('equal', 'box')def update(frame): x = np.cos(frame) y = np.sin(frame) line.set_data([x], [y])return line,ani = animation.FuncAnimation(fig, update, frames=t, blit=True)plt.close()ani.save('circle_animation.mp4', writer='ffmpeg', fps=30, dpi=200)
We include a mp4 video using HTML5 video tags. Here is an example of how to do this:
```{=html}<video width="100%" height="auto" controls autoplay loop muted style="width: 100%; height: auto;" > <source src="circle_animation.mp4" type="video/mp4"> Your browser does not support the video tag.</video>```
Exporting interactive plots - html-js
FuncAnimation from matplotlib.animation can also export animations as interactive HTML-JS files. This allows the reader to interact with the animation directly in the web browser, providing a more engaging experience.
Code
import numpy as npimport matplotlib.pyplot as pltimport matplotlib.animation as animationfrom IPython.display import HTMLfig, ax = plt.subplots()line, = ax.plot([], [], 'ro')ax.set_xlim(-1.5, 1.5)ax.set_ylim(-1.5, 1.5)t = np.linspace(0, 2*np.pi, 60)y = np.sin(t)x = np.cos(t)ax.plot(x, y, 'b-')ax.set_aspect('equal', 'box')def update(frame): x = np.cos(frame) y = np.sin(frame) line.set_data([x], [y])return line,ani = animation.FuncAnimation(fig, update, frames=t, blit=True)plt.close()html_content = ani.to_jshtml(default_mode='loop')HTML(f"""<div style="width:100%"><style>img {{ max-width: 100% !important; }}</style>{html_content}</div><script>setTimeout(() => document.querySelector('button[title="Play"]')?.click(), 100);</script>""")
Warning
These js-html files can be quite large. Be vary carful with how many frames you include in the animation. Above we used 60 frames for the circle animation. Including too many frames will results in an error. You can override this limit but loading times may be long, crash canvas, freeze the browser and generate huge files.
Using mechanicskit.to_responsive_html
Note the boiler plate code below to make the animation responsive in width and to auto start the animation. You can use the MechanicsKit library to simplify this process.
import mechanicskit as mkmk.to_responsive_html(anim, container_id='circle-animation')