One obvious benefit is that it can start rendering "live" and continuously and doesn't have to generate the whole animation before starting to show it.
One obvious benefit is that it can start rendering "live" and continuously and doesn't have to generate the whole animation before starting to show it.
It interoperates with python by being passed data to the draw call.
Usage like: c = Canvas(); c.draw(xs); xs is a numpy array of shape (N, 2), the points to draw on the canvas.
It's not fancy. But it drew animations faster than matplotlib (I ran this for thousands of frames, just to watch the simulation).
import IPython.display as ipydisplay
import json
import numpy as np
class Canvas:
def __init__(self, canvas_id=None, width=500, height=500):
self.display_id = None
self._canvas_id = canvas_id or ('canvas_' + str(np.random.randint(0, 10000)))
display(ipydisplay.HTML(f"""
<canvas id="{self._canvas_id}" width="{width}" height="{height}" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
"""))
def draw(self, xs, world_size):
xs_string = json.dumps(xs.tolist())
obj = ipydisplay.HTML(f"""
<script>
var c = document.getElementById("{self._canvas_id}");
var ctx = c.getContext("2d");
var xs = {xs_string};
var size = {world_size};
ctx.clearRect(0, 0, c.width, c.height);
var scale = c.width / size;
for (let i = 0; i < xs.length; i++) {{
ctx.fillRect(xs[i][0] * scale, c.height - xs[i][1] * scale, 2, 2)
}}
</script>
""")
if self.display_id:
ipydisplay.update_display(obj, display_id=self.display_id)
return self.display_id
self.display_id = display(obj, display_id=True).display_idI went through:
- matplotlib animations (super slow, not live)
- writing an image with PIL and updating display (flickers)
- custom html+canvas code
- ipycanvas
I call display() on the canvas and then run the draw update loop on the canvas later in the same cell. That way the canvas is first displayed and then "live" updated.