In [ ]:
from ipycanvas import Canvas, hold_canvas
In [ ]:
import numpy as np
In [ ]:
from math import pi, cos, sin
In [ ]:
def draw(canvas, t):
    size = 1000
    step = 20
    t1 = t / 1000.

    x = 0
    while x < size + step:
        y = 0
        while y < size + step:
            x_angle = y_angle = 2 * pi

            angle = x_angle * (x / size) + y_angle * (y / size)

            particle_x = x + 20 * cos(2 * pi * t1 + angle)
            particle_y = y + 20 * sin(2 * pi * t1 + angle)

            canvas.fill_circle(particle_x, particle_y, 6)

            y = y + step

        x = x + step
In [ ]:
def fast_draw(canvas, t):
    """Same as draw, but using NumPy and the vectorized version of fill_circle: fill_circles"""
    size = 1000
    step = 20
    t1 = t / 1000.
    
    x = np.linspace(0, size, int(size / step))
    y = np.linspace(0, size, int(size / step))
    xv, yv = np.meshgrid(x, y)
    
    x_angle = y_angle = 2 * pi

    angle = x_angle * (xv / size) + y_angle * (yv / size)

    particle_x = xv + 20 * np.cos(2 * pi * t1 + angle)
    particle_y = yv + 20 * np.sin(2 * pi * t1 + angle)

    canvas.fill_circles(particle_x, particle_y, 6)
In [ ]:
size = 1000
canvas = Canvas(width=size, height=size)
canvas.fill_style = '#fcba03'
canvas

1: Using from time import sleep and fill_circle

Worst approach: Slow locally, slow using a remote server (MyBinder)

In [ ]:
from time import sleep

for i in range(200):
    with hold_canvas(canvas):
        canvas.clear()

        draw(canvas, i * 20.)

    sleep(20 / 1000.)

2: Using canvas.sleep and fill_circle

This caches the entire animation before sending it to the front-end. This results in a slow execution (caching), but it ensure a smooth animation on the front-end whichever the context (local or remote server).

Slow to execute, smooth animation

In [ ]:
with hold_canvas(canvas):
    for i in range(200):
        canvas.clear()

        draw(canvas, i * 20.)

        canvas.sleep(20)
In [ ]:
canvas

3: Using from time import sleep and the vectorized fill_circles

Super fast locally, can be fast on a remote server if the latency is correct

In [ ]:
from time import sleep

for i in range(200):
    with hold_canvas(canvas):
        canvas.clear()

        fast_draw(canvas, i * 20.)

    sleep(20 / 1000.)

4: Using canvas.sleep and the vectorized fill_circles

Best approach: Super fast locally, super fast on a remote server

In [ ]:
with hold_canvas(canvas):
    for i in range(200):
        canvas.clear()

        fast_draw(canvas, i * 20.)

        canvas.sleep(20)

Conclusion

Always use hold_canvas!

As much as possible, try to use the vectorized version of the base methods if you want to exectute them multiple times (fill_circles, fill_rects etc).

If you can, make use of canvas.sleep instead of from time import sleep so that the entire animation is sent at once to the front-end, making a smoother animation whatever the server latency.