Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets
1import time 2 3from ..drawing import Drawing 4from .drawing_widget import DrawingWidget 5 6 7class AsyncAnimation(DrawingWidget): 8 '''A Jupyter notebook widget for asynchronously displaying an animation. 9 10 Example: 11 # Jupyter cell 1: 12 widget = AsyncAnimation(fps=10) 13 widget 14 # [Animation is displayed here] 15 16 # Jupyter cell 2: 17 global_variable = 'a' 18 @widget.set_draw_frame # Animation above is automatically updated 19 def draw_frame(secs=0): 20 # Draw something... 21 d = draw.Drawing(300, 40) 22 d.append(draw.Text(global_variable, 20, 0, 10)) 23 d.append(draw.Text(str(secs), 20, 30, 10)) 24 return d 25 26 # Jupyter cell 3: 27 global_variable = 'b' # Animation above now displays 'b' 28 29 Attributes: 30 fps: The animation frame rate (frames per second). 31 draw_frame: A function that takes a single argument (animation time) and 32 returns a Drawing. 33 paused: While True, the animation will not run. Only the current frame 34 will be shown. 35 disable: While True, the widget will not be interactive and the 36 animation will not update. 37 click_pause: If True, clicking the drawing will pause or resume the 38 animation. 39 mousemove_pause: If True, moving the mouse up across the drawing will 40 pause the animation and moving the mouse down will resume it. 41 mousemove_y_threshold: Controls the sensitivity of mousemove_pause in 42 web browser pixels. 43 ''' 44 45 def __init__(self, fps=10, draw_frame=None, *, paused=False, disable=False, 46 click_pause=True, mousemove_pause=False, 47 mousemove_y_threshold=10): 48 self._fps = fps 49 self._paused = paused 50 if draw_frame is None: 51 def draw_frame(secs): 52 return Drawing(0, 0) 53 self._draw_frame = draw_frame 54 self._last_secs = 0 55 self.click_pause = click_pause 56 self.mousemove_pause = mousemove_pause 57 self.mousemove_y_threshold = mousemove_y_threshold 58 self._start_time = 0 59 self._stop_time = 0 60 self._y_loc = 0 61 self._y_max = 0 62 self._y_min = 0 63 if self._paused: 64 frame_delay = -1 65 else: 66 frame_delay = 1000 // self._fps 67 self._start_time = time.monotonic() 68 initial_drawing = self.draw_frame(0) 69 super().__init__(initial_drawing, throttle=True, disable=disable, 70 frame_delay=frame_delay) 71 72 # Register callbacks 73 @self.mousedown 74 def mousedown(self, x, y, info): 75 if not self.click_pause: 76 return 77 self._y_min = self._y_max = self._y_loc 78 self.paused = not self.paused 79 80 @self.mousemove 81 def mousemove(self, x, y, info): 82 self._y_loc += info['movementY'] 83 if not self.mousemove_pause: 84 self._y_min = self._y_max = self._y_loc 85 return 86 self._y_max = max(self._y_max, self._y_loc) 87 self._y_min = min(self._y_min, self._y_loc) 88 thresh = self.mousemove_y_threshold 89 invert = thresh < 0 90 thresh = max(0.01, abs(thresh)) 91 down_triggered = self._y_loc - self._y_min >= thresh 92 up_triggered = self._y_max - self._y_loc >= thresh 93 if down_triggered: 94 self._y_min = self._y_loc 95 if up_triggered: 96 self._y_max = self._y_loc 97 if invert: 98 down_triggered, up_triggered = up_triggered, down_triggered 99 if down_triggered: 100 self.paused = False 101 if up_triggered: 102 self.paused = True 103 104 @self.timed 105 def timed(self, info): 106 secs = time.monotonic() - self._start_time 107 self.drawing = self.draw_frame(secs) 108 self._last_secs = secs 109 110 @self.on_exception 111 def on_exception(self, e): 112 self.paused = True 113 114 @property 115 def fps(self): 116 return self._fps 117 118 @fps.setter 119 def fps(self, new_fps): 120 self._fps = new_fps 121 if self.paused: 122 return 123 self.frame_delay = 1000 // self._fps 124 125 @property 126 def paused(self): 127 return self._paused 128 129 @paused.setter 130 def paused(self, new_paused): 131 if bool(self._paused) == bool(new_paused): 132 return 133 self._paused = new_paused 134 if self._paused: 135 self.frame_delay = -1 136 self._stop_time = time.monotonic() 137 else: 138 self._start_time += time.monotonic() - self._stop_time 139 self.frame_delay = 1000 // self._fps 140 141 @property 142 def draw_frame(self): 143 return self._draw_frame 144 145 @draw_frame.setter 146 def draw_frame(self, new_draw_frame): 147 self._draw_frame = new_draw_frame 148 if self.paused: 149 # Redraw if paused 150 self.drawing = self._draw_frame(self._last_secs) 151 152 def set_draw_frame(self, new_draw_frame): 153 self.draw_frame = new_draw_frame 154 return new_draw_frame