The Times Tables, Mandelbrot and the Heart of Mathematics. The good old **times tables** lead a very exciting secret life involving the infamous **Mandelbrot** set.

It's a good practice to place all the imports at the top of the document to better trace dependencies and keep them updated, and also to know which tools are required. In this case there are General Purpose imports and Jupyter specifics.

```
# General Purpose
#
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation, rc
import matplotlib.lines as mlines
import colorsys
from matplotlib.collections import LineCollection
# Jupyter Specifics
#
import matplotlib as mpl
from IPython.display import HTML
from ipywidgets.widgets import interact, IntSlider, FloatSlider, Dropdown, Layout
# Some magics
#
%matplotlib inline
```

```
# nbi:hide_in
# The method (of the animation instances) to manage the
# player is controlled by the animation rc parameter.
#
# The rc parameter currently supports values of "none", "html5"
# and "jshtml".
#
# none: no player (display) is shown
# html5: use the native HTML5 player widget
# jshtml: use the interactive JavaScript widget
#
# The default is none to not display a player. To display
# the native HTML5 player, # set it to "html5". For the
# interactive JavaScript widget to "jshtml".
#
rc('animation', html='html5', embed_limit='256')
# rc('animation', html='jshtml', embed_limit='512')
```

Once everything is imported and ready to use, several functions must be defined, namely:

- A function to calculate the points arround a circle
- A function to generate each of the lines
- A function to plot the labels and the point in the circle
- A function to plot the lines in the circle

The first function is called *points_arround_circle* and it basically uses polar coordinates to place a given number of points arround a circle of a given radius. Here numpy is needed to make the calculation performant.

```
def points_arround_circle(number=100, center=(0,0), radius=1):
theta = np.linspace(0, 2 * np.pi - (2 * np.pi / number), number)
x = radius * np.cos(theta)
y = radius * np.sin(theta)
return (x, y)
```

Second, in order to generate the lines, the list of points is given and a new line is generated by the function *get_lines_from_points*.

```
def get_lines_from_points(x, y, factor, animated=None):
limit = len(x)
if animated is not None:
for i in range(limit):
x_range = (x[i], x[int(i * factor) % limit])
y_range = (y[i], y[int(i * factor) % limit])
yield mlines.Line2D(x_range, y_range)
else:
for i in range(limit):
start = (x[i], y[i])
index = int((i * factor) % limit)
end = (x[index], y[index])
yield end, start
```

Now it's time to plot the point around the circle by *plot_circle_points*. Both in the circle, the points and the labels are plotted.

```
def plot_circle_points(x, y, ax, labels=None):
ax.annotate("Points: {}".format(len(x)), (0.8, 0.9))
ax.plot(x, y, "-ko", markevery=1)
if not labels is None:
for i, (x, y) in enumerate(zip(x, y)):
ax.annotate(i, (x, y))
```

Finally, a function *plot_lines* which receives the axis object to plot all the lines. With the option (if given), a color for the lines in a HSV format is calculated.

```
def plot_lines(x, y, factor, ax, color=None):
ax.annotate("Factor: {}".format(factor), (0.8, 1))
lines = list(get_lines_from_points(x, y, factor))
if color is None:
line_segments = LineCollection(lines)
else:
line_segments = LineCollection(lines, colors=colorsys.hsv_to_rgb(color, 1.0, 0.8))
ax.add_collection(line_segments)
```

After all the functions needed are defined, now plotting is quite simple. Just generate the axis object and invoke the functions in the logical order, and you get the image.

One approach is manually changing the factor and points variables and then executing the plot. Since Jupyter provides support for **interaction**, a more user-friendly approach can be used. Change the image by moving the **sliders** to either side.

```
def plot_parametric(Factor=2, Points=100):
# figsize: width|height recalculated from inches to pixels
my_dpi=96
plt.figure(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi)
ax = plt.subplot()
plt.axis('off')
x, y = points_arround_circle(number=Points)
plot_circle_points(x, y, ax)
plot_lines(x, y, Factor, ax)
plt.show()
factors = 2, 3, 4, 5, 8, 10, 16, 20, 21, 25, 26, 34
print("\nTry these Factors with different number of Points:\n", *factors, "\n")
```

```
# nbi:hide_in
interact(plot_parametric,
Factor=IntSlider(min=1, max=34, step=1, value=2, layout=Layout(width='90%')),
Points=IntSlider(min=25, max=200, step=5, value=100, layout=Layout(width='90%')));
```

The factor and the number of points is fixed for the plot by your selection, but each **line** is plotted per iteration. Try different factors (for the times table) and vary the number of points placed on the circle.

```
# nbi:hide_in
# animation function. This is called sequentially.
#
def animate_line_by_line(i, lines, ax):
ax.add_line(next(lines))
return []
def line_by_line(Factor, Points, Interval):
# figsize: width|height recalculated from inches to pixels
my_dpi=96
fig, ax = plt.subplots(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi);
plt.axis('off')
x, y = points_arround_circle(number=Points)
plot_circle_points(x, y, ax)
ax.annotate("Factor: {}".format(Factor), (0.8, 1))
ax.annotate("Delay: {}".format(Interval), (0.8, 0.8))
lines = get_lines_from_points(x, y, Factor, animated=True)
# call the animator. blit=True means only re-draw the parts that have changed.
#
anim = animation.FuncAnimation(
fig, animate_line_by_line, frames=len(x)-2,
interval=Interval, blit=True, fargs=(lines, ax)
);
plt.close()
return anim
```

```
# nbi:hide_in
anim = line_by_line(Factor=2, Points=100, Interval=500)
interact(line_by_line,
Factor=Dropdown(
value=2,
options=[2, 3, 4, 5, 8, 10, 16, 20, 21, 25, 26, 34],
description='Factor'
),
Points=Dropdown(
value=100,
options=[5, 15, 25, 50, 75, 100, 150, 200],
description='Points'
),
Interval=Dropdown(
value=150,
options=[300, 200, 150, 100, 75],
description='Delay'
)
);
```

```
# nbi:hide_in
Writer = animation.writers['ffmpeg']
writer = Writer(fps=30)
anim.save('line_by_line.mp4', writer=writer)
```

```
```