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