Skip to content

Commit 74cb7f7

Browse files
authored
Merge pull request #126 from martinRenou/sleep
Implement sleep method
2 parents d6633d6 + 7a19b2f commit 74cb7f7

File tree

3 files changed

+263
-2
lines changed

3 files changed

+263
-2
lines changed

examples/animation.ipynb

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"from ipycanvas import Canvas, hold_canvas"
10+
]
11+
},
12+
{
13+
"cell_type": "code",
14+
"execution_count": null,
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"import numpy as np"
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": null,
24+
"metadata": {},
25+
"outputs": [],
26+
"source": [
27+
"from math import pi, cos, sin"
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"metadata": {},
34+
"outputs": [],
35+
"source": [
36+
"def draw(canvas, t):\n",
37+
" size = 1000\n",
38+
" step = 20\n",
39+
" t1 = t / 1000.\n",
40+
"\n",
41+
" x = 0\n",
42+
" while x < size + step:\n",
43+
" y = 0\n",
44+
" while y < size + step:\n",
45+
" x_angle = y_angle = 2 * pi\n",
46+
"\n",
47+
" angle = x_angle * (x / size) + y_angle * (y / size)\n",
48+
"\n",
49+
" particle_x = x + 20 * cos(2 * pi * t1 + angle)\n",
50+
" particle_y = y + 20 * sin(2 * pi * t1 + angle)\n",
51+
"\n",
52+
" canvas.fill_circle(particle_x, particle_y, 6)\n",
53+
"\n",
54+
" y = y + step\n",
55+
"\n",
56+
" x = x + step"
57+
]
58+
},
59+
{
60+
"cell_type": "code",
61+
"execution_count": null,
62+
"metadata": {},
63+
"outputs": [],
64+
"source": [
65+
"def fast_draw(canvas, t):\n",
66+
" \"\"\"Same as draw, but using NumPy and the vectorized version of fill_circle: fill_circles\"\"\"\n",
67+
" size = 1000\n",
68+
" step = 20\n",
69+
" t1 = t / 1000.\n",
70+
" \n",
71+
" x = np.linspace(0, size, int(size / step))\n",
72+
" y = np.linspace(0, size, int(size / step))\n",
73+
" xv, yv = np.meshgrid(x, y)\n",
74+
" \n",
75+
" x_angle = y_angle = 2 * pi\n",
76+
"\n",
77+
" angle = x_angle * (xv / size) + y_angle * (yv / size)\n",
78+
"\n",
79+
" particle_x = xv + 20 * np.cos(2 * pi * t1 + angle)\n",
80+
" particle_y = yv + 20 * np.sin(2 * pi * t1 + angle)\n",
81+
"\n",
82+
" canvas.fill_circles(particle_x, particle_y, 6)"
83+
]
84+
},
85+
{
86+
"cell_type": "code",
87+
"execution_count": null,
88+
"metadata": {},
89+
"outputs": [],
90+
"source": [
91+
"size = 1000\n",
92+
"canvas = Canvas(width=size, height=size)\n",
93+
"canvas.fill_style = '#fcba03'\n",
94+
"canvas"
95+
]
96+
},
97+
{
98+
"cell_type": "markdown",
99+
"metadata": {},
100+
"source": [
101+
"### 1: Using `from time import sleep` and `fill_circle`\n",
102+
"\n",
103+
"**Worst approach: Slow locally, slow using a remote server (MyBinder)**"
104+
]
105+
},
106+
{
107+
"cell_type": "code",
108+
"execution_count": null,
109+
"metadata": {},
110+
"outputs": [],
111+
"source": [
112+
"from time import sleep\n",
113+
"\n",
114+
"for i in range(200):\n",
115+
" with hold_canvas(canvas):\n",
116+
" canvas.clear()\n",
117+
"\n",
118+
" draw(canvas, i * 20.)\n",
119+
"\n",
120+
" sleep(20 / 1000.)"
121+
]
122+
},
123+
{
124+
"cell_type": "markdown",
125+
"metadata": {},
126+
"source": [
127+
"### 2: Using `canvas.sleep` and `fill_circle`\n",
128+
"\n",
129+
"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).\n",
130+
"\n",
131+
"**Slow to execute, smooth animation**"
132+
]
133+
},
134+
{
135+
"cell_type": "code",
136+
"execution_count": null,
137+
"metadata": {},
138+
"outputs": [],
139+
"source": [
140+
"with hold_canvas(canvas):\n",
141+
" for i in range(200):\n",
142+
" canvas.clear()\n",
143+
"\n",
144+
" draw(canvas, i * 20.)\n",
145+
"\n",
146+
" canvas.sleep(20)"
147+
]
148+
},
149+
{
150+
"cell_type": "code",
151+
"execution_count": null,
152+
"metadata": {},
153+
"outputs": [],
154+
"source": [
155+
"canvas"
156+
]
157+
},
158+
{
159+
"cell_type": "markdown",
160+
"metadata": {},
161+
"source": [
162+
"### 3: Using `from time import sleep` and the vectorized `fill_circles`\n",
163+
"\n",
164+
"**Super fast locally, can be fast on a remote server if the latency is correct**"
165+
]
166+
},
167+
{
168+
"cell_type": "code",
169+
"execution_count": null,
170+
"metadata": {},
171+
"outputs": [],
172+
"source": [
173+
"from time import sleep\n",
174+
"\n",
175+
"for i in range(200):\n",
176+
" with hold_canvas(canvas):\n",
177+
" canvas.clear()\n",
178+
"\n",
179+
" fast_draw(canvas, i * 20.)\n",
180+
"\n",
181+
" sleep(20 / 1000.)"
182+
]
183+
},
184+
{
185+
"cell_type": "markdown",
186+
"metadata": {},
187+
"source": [
188+
"### 4: Using `canvas.sleep` and the vectorized `fill_circles`\n",
189+
"\n",
190+
"**Best approach: Super fast locally, super fast on a remote server**"
191+
]
192+
},
193+
{
194+
"cell_type": "code",
195+
"execution_count": null,
196+
"metadata": {},
197+
"outputs": [],
198+
"source": [
199+
"with hold_canvas(canvas):\n",
200+
" for i in range(200):\n",
201+
" canvas.clear()\n",
202+
"\n",
203+
" fast_draw(canvas, i * 20.)\n",
204+
"\n",
205+
" canvas.sleep(20)"
206+
]
207+
},
208+
{
209+
"cell_type": "markdown",
210+
"metadata": {},
211+
"source": [
212+
"### Conclusion\n",
213+
"\n",
214+
"Always use `hold_canvas`!\n",
215+
"\n",
216+
"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).\n",
217+
"\n",
218+
"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."
219+
]
220+
}
221+
],
222+
"metadata": {
223+
"kernelspec": {
224+
"display_name": "Python 3",
225+
"language": "python",
226+
"name": "python3"
227+
},
228+
"language_info": {
229+
"codemirror_mode": {
230+
"name": "ipython",
231+
"version": 3
232+
},
233+
"file_extension": ".py",
234+
"mimetype": "text/x-python",
235+
"name": "python",
236+
"nbconvert_exporter": "python",
237+
"pygments_lexer": "ipython3",
238+
"version": "3.9.0"
239+
}
240+
},
241+
"nbformat": 4,
242+
"nbformat_minor": 4
243+
}

ipycanvas/canvas.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
'bezierCurveTo': 26, 'fillText': 27, 'strokeText': 28, 'setLineDash': 29, 'drawImage': 30,
2828
'putImageData': 31, 'clip': 32, 'save': 33, 'restore': 34, 'translate': 35,
2929
'rotate': 36, 'scale': 37, 'transform': 38, 'setTransform': 39, 'resetTransform': 40,
30-
'set': 41, 'clear': 42,
30+
'set': 41, 'clear': 42, 'sleep': 43,
3131
}
3232

3333

@@ -253,6 +253,10 @@ def __init__(self, *args, **kwargs):
253253

254254
self.on_msg(self._handle_frontend_event)
255255

256+
def sleep(self, time):
257+
"""Make the Canvas sleep for `time` milliseconds."""
258+
self._send_canvas_command(COMMANDS['sleep'], [time])
259+
256260
# Rectangles methods
257261
def fill_rect(self, x, y, width, height=None):
258262
"""Draw a filled rectangle of size ``(width, height)`` at the ``(x, y)`` position."""

src/widget.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const COMMANDS = [
4747
'bezierCurveTo', 'fillText', 'strokeText', 'setLineDash', 'drawImage',
4848
'putImageData', 'clip', 'save', 'restore', 'translate',
4949
'rotate', 'scale', 'transform', 'setTransform', 'resetTransform',
50-
'set', 'clear',
50+
'set', 'clear', 'sleep',
5151
];
5252

5353

@@ -174,6 +174,9 @@ class CanvasModel extends DOMWidgetModel {
174174
const name: string = COMMANDS[command[0]];
175175
const args: any[] = command[1];
176176
switch (name) {
177+
case 'sleep':
178+
await this.sleep(args[0]);
179+
break;
177180
case 'fillRect':
178181
this.fillRect(args[0], args[1], args[2], args[3]);
179182
break;
@@ -234,6 +237,17 @@ class CanvasModel extends DOMWidgetModel {
234237
}
235238
}
236239

240+
private async sleep(time: number) {
241+
this.forEachView((view: CanvasView) => {
242+
view.updateCanvas();
243+
});
244+
245+
this.trigger('new-frame');
246+
this.syncImageData();
247+
248+
await new Promise(resolve => setTimeout(resolve, time));
249+
}
250+
237251
protected fillRect(x: number, y: number, width: number, height: number) {
238252
this.ctx.fillRect(x, y, width, height);
239253
}

0 commit comments

Comments
 (0)