Making things more... fun?
from ipycanvas import Canvas
canvas = Canvas(width=200, height=150)
canvas.fill_rect(25, 25, 100, 100)
canvas.clear_rect(45, 45, 60, 60)
canvas.stroke_rect(50, 50, 50, 50)
canvas
Apparently it's fast... Let's play with some perlin noise from 05.
import days_of_code
from days_of_code.perlin import *
import numpy as np
n_particles = 5_000
steps = 50
delta = 1
x = np.array(np.random.rayleigh(250, n_particles))
y = np.array(np.random.rayleigh(250, n_particles))
size = np.random.randint(1, 3, n_particles)
canvas = Canvas(width=800, height=500, sync_image_data=True)
for i in range(steps):
# Draw
canvas.fill_style = 'green'
canvas.fill_rects(x, y, size)
# Compute new locations
angles = days_of_code.perlin.perlin(x/100, y/100) * 3
x += np.sin(angles)*delta
y += np.cos(angles)*delta
canvas
canvas.to_file('outputs/perlin_ipycanvas1.png') # Saving to file requires sync_image_data=True when making the canvas
Nice! But the real fun is in making things move
from time import sleep
from ipycanvas import Canvas, hold_canvas
canvas = Canvas(width=800, height=500)
display(canvas)
x = np.array(np.random.rayleigh(250, n_particles))
y = np.array(np.random.rayleigh(250, n_particles))
size = np.random.randint(1, 3, n_particles)
for i in range(100): # while(True) for infinite
with hold_canvas(canvas):
# Clear the old animation step
canvas.clear()
canvas.fill_style = 'green'
canvas.fill_rects(x, y, size)
# Compute new locations
angles = days_of_code.perlin.perlin(x/100, y/100) * 3
x += np.sin(angles)*delta
y += np.cos(angles)*delta
# Animation frequency ~50Hz = 1./50. seconds
sleep(0.02)
Interaction
I tried to add some interaction during animation with both ipycanvas' mouse events and ipevents - neither worked. It turns out using time.sleep() to run animations blocks everything and the events only get fired once the animation stops. To fix this we can use the threading library to handle running the animation loop in a separate thread.
from ipywidgets import Label, HTML
from copy import deepcopy
# Setting up the canvas and event handling
canvas = Canvas(width=800, height=500)
h = HTML('Event info')
x = np.array(np.random.rayleigh(250, n_particles))
y = np.array(np.random.rayleigh(250, n_particles))
size = np.random.randint(1, 3, n_particles)
def update_canvas():
with hold_canvas(canvas):
canvas.clear() # Clear the old animation step
canvas.fill_style = 'green'
canvas.fill_rects(x, y, size) # Draw the new frame
def handle_mouse_down(xpos, ypos):
global x, y
x_new = np.array(np.random.rayleigh(30, 100))+xpos-15
y_new = np.array(np.random.rayleigh(30, 100))+ypos-15
x = np.concatenate([x[-4000:], x_new])
y = np.concatenate([y[-4000:], y_new])
size = np.random.randint(1, 3, len(x))
update_canvas()
# x = np.array(np.random.rayleigh(250, n_particles))
# y = np.array(np.random.rayleigh(250, n_particles))
# size = np.random.randint(1, 3, n_particles)
h.value = str(xpos) + '_' + str(x.shape) + '_' + str(x.shape)
# x = np.array(np.random.rayleigh(250, n_particles))
# y = np.array(np.random.rayleigh(250, n_particles))
# size = np.random.randint(1, 3, n_particles)
canvas.on_mouse_down(handle_mouse_down)
display(canvas, h)
def update():
global x, y
update_canvas()
# Compute new locations
angles = days_of_code.perlin.perlin(x/100, y/100) * 3
x += np.sin(angles)*delta
y += np.cos(angles)*delta
interval = 0.02
rt = RepeatedTimer(interval, update) # it auto-starts, no need of rt.start()
rt.stop()