#!/usr/bin/env python # coding: utf-8 # ## An animation reproducing a Fermat's Library gif, posted on twitter ## # Update: The explanation of the rectilinear motion of the eight points in this animation is given [here](http://nbviewer.jupyter.org/github/empet/Math/blob/master/hypocycloid-online.ipynb). # Fermat's library, `@fermatslibrary`, posted on twitter a gif [https://twitter.com/fermatslibrary/status/862659602776805379](https://twitter.com/fermatslibrary/status/862659602776805379) illustrating # eight points moving inside a circle. No explanation of the motion was given. Here I reproduce it via Python Plotly. # # Following each particular point one notices that it moves on a diameter of the greater circle. # # The white rays intersect the circle (of radius R=1) at the $16^{th}$ roots of unity. The eight points lie at each time during their motion on a circle of radius 1/2, and the center at the middle of a ray of the greater circle. # # If at the time t=0, corresponding to the initial frame, the smaller circle has the center $(0.5, 0)$, and radius r=1/2, then the points at this time are represented by the complex numbers $u[k]=0.5+0.5\cdot e^{2\pi j k/8}$, where $e^{2\pi j k/8}$, $k=\overline{0,7}$, are the $8^{th}$ roots of unity. The $m^{th}$ frame of the animation displays the position of points obtained from the initial ones, as follows: # - rotate the center 0.5+0j about O, by $\theta_m=-2 m \pi/16$ and get the new center $C_m$; # - place on the circle of center $C_m$ and radius r=0.5, the points $u[k]-0.5$ rotated about O with $-\theta_m$. # Hence in the $m^{th}$ frame we plot the points $w[k]=e^{-m\pi j/8}0.5+e^{m\pi j/8}(u[k]-0.5)$, $k=\overline{0,7}$ # In[1]: import numpy as np from numpy import pi import plotly.graph_objects as go # In[2]: u = np.array([0.5 + 0.5*np.exp(2*k*np.pi*1j/8) for k in range(8)], dtype=np.complex) # Define data that will be updated by each animation frame: # In[4]: fig = go.Figure(go.Scatter( x = u.real, y= u.imag, mode='markers', marker=dict( size=0.01, color='white'), name='moving_pts', )) # In[5]: frames=[] for m in range(64): w = np.exp(-1j*m*pi/8)*0.5+np.exp(1j*m*pi/8)*(u-0.5) frames.append(go.Frame(data=[go.Scatter( x= w.real, y=w.imag, marker=dict(size=15, color='white') )], traces=[0])) fig.update(frames=frames); # Set the plot layout: # In[6]: axis = dict(visible=False, range=[-1.1, 1.01], autorange=False) fig.update_layout( title='Math is Fun', width=600, height=600, showlegend=False, xaxis=axis, yaxis=axis, hovermode='closest', updatemenus=[dict(type='buttons', showactive=False, y=1, x=1.2, xanchor='right', yanchor='top', pad=dict(l=10), buttons=[dict(label='Play', method='animate', args=[None, dict(frame=dict(duration=150, redraw=False), transition=dict(duration=80), fromcurrent=True, mode='immediate' )] )] )] ); # The black disk is defined as a Plotly shape, and the withe diameters as quadratic Bézier curves defined by # three colinear control points: # In[7]: z = np.array([np.exp(2*k*np.pi*1j/16) for k in range(16)], dtype=np.complex) # the 16^th roots of unity # In[8]: fig.add_shape(dict(type= 'circle', layer= 'below', xref= 'x', yref='y', fillcolor= 'rgba(10,10,10, 0.9)', x0=-1.01, y0= -1.01, x1= 1.01, y1= 1.01, line_color= 'rgba(10,10,10, 0.9)')) #define the shapes for the eight diameters for k in range(8): x0 = z[k].real y0 = z[k].imag x1 = z[k+8].real y1 = z[k+8].imag fig.add_shape(dict(type= 'path', path= f'M{x0}, {y0} Q 0.0, 0.0 {x1}, {y1}', line= dict(color= 'white', width=0.75) )) # # In[9]: from plotly.offline import download_plotlyjs, init_notebook_mode, iplot init_notebook_mode(connected=True) iplot(fig) # In[10]: from IPython.core.display import HTML def css_styling(): styles = open("./custom.css", "r").read() return HTML(styles) css_styling() # In[ ]: