Python Event Loops

This guide demonstrates how to create Python-controlled animations using Colight plots, the .reset method, and interactive sliders. We'll cover: 1. Setting up a basic animated plot 2. Creating interactive animations with ipywidgets

We must use the "widget" rendering modes for bidirectional python/javascript communication:

import colight.plot as Plot
Plot.configure({"display_as": "widget"})

First, a simple sine wave plot:

import numpy as np
x = np.linspace(0, 10, 100)
basic_plot = (
    Plot.line(list(zip(x, np.sin(x))))
    + Plot.domain([0, 10], [-1, 1])
    + Plot.height(200)
)
basic_plot

Now, let's animate it:

import asyncio
import time
async def animate(duration=5):
    start_time = time.time()
    while time.time() - start_time < duration:
        t = time.time() - start_time
        y = np.sin(x + t)
        basic_plot.reset(
            Plot.line(list(zip(x, y)))
            + Plot.domain([0, 10], [-1, 1])
            + Plot.height(200)
        )
        await asyncio.sleep(1 / 30)  # 30 FPS
future = asyncio.ensure_future(animate())

We use the reset method of a plot to update its content in-place, inside an async function containing a while loop, using sleep to control the frame rate. To avoid interference with Jupyter comms, we use ensure_future to run the function in a new thread.

Let's make it interactive, using ipywidgets sliders to control frequency and amplitude:

import ipywidgets as widgets
interactive_plot = (
    Plot.line(list(zip(x, np.sin(x))))
    + Plot.domain([0, 10], [-2, 2])
    + Plot.height(200)
)
frequency_slider = widgets.FloatSlider(
    value=1.0, min=0.1, max=5.0, step=0.1, description="Frequency:"
)
amplitude_slider = widgets.FloatSlider(
    value=1.0, min=0.1, max=2.0, step=0.1, description="Amplitude:"
)

Now, in our animation loop we use the slider values to compute the y value:

from IPython.display import display
async def interactive_animate(duration=10):
    start_time = time.time()
    while time.time() - start_time < duration:
        t = time.time() - start_time
        y = amplitude_slider.value * np.sin(frequency_slider.value * (x + t))
        interactive_plot.reset(
            Plot.line(list(zip(x, y)))
            + Plot.domain([0, 10], [-2, 2])
            + Plot.height(200)
        )
        await asyncio.sleep(1 / 30)
display(interactive_plot)
display(frequency_slider, amplitude_slider)
future = asyncio.ensure_future(interactive_animate())