#!/usr/bin/env python # coding: utf-8 # ## Creating animated plot from frames that displays with control widget in Jupyter # # This notebook shows animations made in the style covered [here](http://louistiao.me/posts/notebooks/embedding-matplotlib-animations-in-jupyter-as-interactive-javascript-widgets/). Learned of from [this Stack0verflow post](https://stackoverflow.com/q/70761535/8508004) seeking help. # # # **Important benefit of this approach**: # These animations are interactive and playable in static renderings of notebooks such as those displayed via [nbviewer](https://nbviewer.jupyter.org/). (GitHub's viewer currently doesn't support this; you must use [nbviewer](https://nbviewer.jupyter.org/).) *No active kernel is needed once the notebook as been run once and saved.* # # ------- # # # This can be run in sessions launched from [my animated_matplotlib-binder repo here](https://github.com/fomightez/animated_matplotlib-binder). # # It can be obtained by running the following in a notebook cell the launched session: # # ```text # !curl -OL https://gist.githubusercontent.com/fomightez/d862333d8eefb94a74a79022840680b1/raw/8fd2185dd4d6d1c8aa78a02a061a59546898f19f/Creating%2520animated%2520plot%2520from%2520frames%2520that%2520displays%2520with%2520control%2520widget%2520in%2520Jupyter.ipynb # ``` # # Following that, run the notebook from the Jupyter Dashboard or the file navigation pane, depending if you are using the classic notebook interface or JupyterLab, respectively. # # ------ # # ### Code based from https://stackoverflow.com/a/70764815/8508004 with interactive widget controller # # This runs the example that was sought to display, although it has been altered to produce the animation and not an HTML5 movie, like [the original post](https://stackoverflow.com/a/70764815/8508004). If you are anyone except Wayne, you probably seek this one. (I, Wayne like the idea of leaving a read trail where the animation has covered and so kept the example below even though it isn't 'correct' because hard to see what point that one is at when scrubbing in reverse.) # # Here the red dot makes it easy to follow from where on the plot of the curve at the bottom the upper values are being derived. You can easily 'scrub' back and forth. This is much more full-featured for complex comparative plots like this. The ability to scrub can also be very useful when wanting to discuss particular points in the plot. The examples at the main page that currently launch from my [animated_matplotlib-binder](https://github.com/fomightez/animated_matplotlib-binder) play through once triggered and you cannot pause. # In[1]: import scipy.integrate import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation plt.rcParams["animation.html"] = "jshtml" plt.rcParams['figure.dpi'] = 150 plt.ioff() def showConvolution(t0,f1, f2): # Calculate the overall convolution result using Simpson integration convolution = np.zeros(len(t)) for n, t_ in enumerate(t): prod = lambda tau: f1(tau) * f2(t_-tau) convolution[n] = scipy.integrate.simps(prod(t), t) # Create the shifted and flipped function f_shift = lambda t: f2(t0-t) prod = lambda tau: f1(tau) * f2(t0-tau) # Plot the curves axes[0].clear() # il axes[1].clear() axes[0].set_xlim(-5, 5) axes[0].set_ylim(0, 1.0) #axes[0].set_ymargin(0.05) # il axes[0].plot(t, f1(t), label=r'$f_1(\tau)$') axes[0].plot(t, f_shift(t), label=r'$f_2(t_0-\tau)$') #axes[0].fill(t, prod(t), color='r', alpha=0.5, edgecolor='black', hatch='//') # il axes[0].plot(t, prod(t), 'r-', label=r'$f_1(\tau)f_2(t_0-\tau)$') #axes[0].grid(True); axes[0].set_xlabel(r'$\tau$'); axes[0].set_ylabel(r'$x(\tau)$') # il #axes[0].legend(fontsize=10) # il #axes[0].text(-4, 0.6, '$t_0=%.2f$' % t0, bbox=dict(fc='white')) # il # plot the convolution curve axes[1].set_xlim(-5, 5) axes[1].set_ylim(0, 0.4) #axes[1].set_ymargin(0.05) # il axes[1].plot(t, convolution, label='$(f_1*f_2)(t)$') # recalculate the value of the convolution integral at the current time-shift t0 current_value = scipy.integrate.simps(prod(t), t) axes[1].plot(t0, current_value, 'ro') # plot the point #axes[1].grid(True); axes[1].set_xlabel('$t$'); axes[1].set_ylabel('$(f_1*f_2)(t)$') # il #axes[1].legend(fontsize=10) # il #plt.show() # il Fs = 50 # our sampling frequency for the plotting T = 5 # the time range we are interested in t = np.arange(-T, T, 1/Fs) # the time samples f1 = lambda t: np.maximum(0, 1-abs(t)) f2 = lambda t: (t>0) * np.exp(-2*t) t0 = np.arange(-2.0,2.0, 0.05) fig = plt.figure(figsize=(8,3)) axes= fig.subplots(2, 1) anim = animation.FuncAnimation(fig, showConvolution, frames=t0, fargs=(f1,f2),interval=80) anim # See the post for the rest of the code at the bottom: # # ```python # #anim.save('animation.mp4', fps=30) # fps = frames per second # #plt.show() # # from IPython.display import HTML # # plt.close() # HTML(anim.to_html5_video()) # ``` # # This makes a video of the animation. I presume that animation video is what is included at the bottom of [the post](https://stackoverflow.com/a/70764815/8508004). # I presume the `anim.save` line if uncommented produces a file that can be used by embeddable web-based players or played one's local machine. However, ffmpeg is not installed in the recommended session and so I have not fully tried. Running [the original code this is adapted from](https://stackoverflow.com/a/70764815/8508004) in sessions here (assuming from the recommended launch) results in `RuntimeError: Requested MovieWriter (ffmpeg) not available`. The last three lines of [that current code](https://stackoverflow.com/a/70764815/8508004) can be deleted and then the animation will be shown when run in a fresh session with **no control widget**. # ----- # # ### Example with red highlighting region run # # (This actually came first and separate from the example above. I liked that it produces a widget. As well as the effect of highlighting all of what has run.) # # Notes on running and details from when working out to answer https://stackoverflow.com/q/70761535/8508004: # # Slide the blue slider below the two sub-lots and above the control buttons after the plots and interface are generated. Generation takes like fifteen seconds; **be patient**. # I'm putting it here primarily as an example of ability to use slider controller to progress through. Adapted from a version from [here](https://stackoverflow.com/q/70761535/8508004). I think it's an interesting approach to use rendering the current point along the curve as red sphere to illustrate the point it has reached on the curve as progresses along the x axis (t, in this cases). (The changing of the color of the bottom plot, and slight movement of the y-axis label, I think is an artifact, or maybe it is comes from the line `plt.plot(t, convolution, label='$(f_1*f_2)(t)$')` not specifying a color. It adds a neat vibrant effect.) I don't quite get the math; however, I think it is meant to show something similar to the [gif here](https://stackoverflow.com/a/69580261/8508004) where illustrates integral for differant span as it progresses with a window along a curve. (Also note this is a very different approach than plots earlier because here curve(s) show the whole time and not adding the additional points a x-axis progresses, however, would seem only way to get the animation like in [the gif here](https://stackoverflow.com/a/69580261/8508004). It could be implemented in the simple approach lime up top by calulating entire curve for each moment and then use index to get full curve and not just a point as enumerare x.) # # I, Wayne, like this idea of the red region showing what has already been covered, and so that is the only reason I kept this. I feel like that trick could be useful in a plot animation somewhere. # In[2]: get_ipython().run_line_magic('matplotlib', 'notebook') import scipy.integrate import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation plt.rcParams["animation.html"] = "jshtml" plt.rcParams['figure.dpi'] = 150 plt.ioff() def showConvolution(t0, f1, f2): # Calculate the overall convolution result using Simpson integration convolution = np.zeros(len(t)) for n, t_ in enumerate(t): prod = lambda tau: f1(tau) * f2(t_-tau) convolution[n] = scipy.integrate.simps(prod(t), t) # Create the shifted and flipped function f_shift = lambda t: f2(t0-t) prod = lambda tau: f1(tau) * f2(t0-tau) # Plot the curves plt.subplot(211) plt.cla() plt.plot(t, f1(t), label=r'$f_1(\tau)$') plt.plot(t, f_shift(t), label=r'$f_2(t_0-\tau)$') plt.plot(t, prod(t), 'r-', label=r'$f_1(\tau)f_2(t_0-\tau)$') # plot the convolution curve plt.subplot(212) plt.plot(t, convolution, label='$(f_1*f_2)(t)$') # recalculate the value of the convolution integral at the current time-shift t0 current_value = scipy.integrate.simps(prod(t), t) plt.plot(t0, current_value, 'ro') # plot the point Fs = 50 # our sampling frequency for the plotting T = 5 # the time range we are interested in t = np.arange(-T, T, 1/Fs) # the time samples f1 = lambda t: np.maximum(0, 1-abs(t)) f2 = lambda t: (t>0) * np.exp(-2*t) t0 = np.arange(-2.0,2.0, 0.05) fig = plt.figure(figsize=(8,3)) anim = animation.FuncAnimation(fig, showConvolution, frames=t0, fargs=(f1,f2),interval=80) anim # The one above is 'special' to leave on highlighting of the region. # However, the main point of the highlighting is meant to highlight the current point where the derivative is being calculated. The one at the top of this notebook shows the actual highlight of the point that was more the purpose of the highlight in this case. (I just liked the trailing, region highlighting look although it makes it hard to interpret what is supposed to be read when reversing because no longer really showing what just has been run, which was the point of that highlighted region. The above section makes it look like how it should.)