Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets
at 1.8.2 5.5 kB view raw
1from ipywidgets import widgets 2from traitlets import Unicode, Bool, Int 3 4 5# Register front end javascript 6from IPython import display 7from . import drawing_javascript 8display.display(display.Javascript(drawing_javascript.javascript)) 9del drawing_javascript 10 11 12class DrawingWidget(widgets.DOMWidget): 13 _view_name = Unicode('DrawingView').tag(sync=True) 14 _view_module = Unicode('drawingview').tag(sync=True) 15 _view_module_version = Unicode('0.1.0').tag(sync=True) 16 _image = Unicode().tag(sync=True) 17 _mousemove_blocked = Int(0).tag(sync=True) 18 _frame_blocked = Int(0).tag(sync=True) 19 throttle = Bool(True).tag(sync=True) 20 disable = Bool(False).tag(sync=True) 21 frame_delay = Int(-1).tag(sync=True) 22 23 def __init__(self, drawing, throttle=True, disable=False, frame_delay=-1): 24 ''' 25 DrawingWidget is an interactive Jupyter notebook widget. It works 26 similarly to displaying a Drawing as a cell output but DrawingWidget 27 can register callbacks for user mouse events. Within a callback modify 28 the drawing then call .refresh() to update the output in real time. 29 30 Arguments: 31 drawing: The initial Drawing to display. Call .refresh() after 32 modifying or just assign a new Drawing. 33 throttle: If True, limit the rate of mousemove events. For drawings 34 with many elements, this will significantly reduce lag. 35 disable: While True, mouse events will be disabled. 36 frame_delay: If greater than or equal to zero, a timed callback will 37 occur frame_delay milliseconds after the previous drawing 38 update. 39 ''' 40 super().__init__() 41 self.throttle = throttle 42 self.disable = disable 43 self.frame_delay = frame_delay 44 self.drawing = drawing 45 self.mousedown_callbacks = [] 46 self.mousemove_callbacks = [] 47 self.mouseup_callbacks = [] 48 self.timed_callbacks = [] 49 self.exception_callbacks = [] 50 51 self.on_msg(self._receive_msg) 52 53 @property 54 def drawing(self): 55 return self._drawing 56 57 @drawing.setter 58 def drawing(self, drawing): 59 self._drawing = drawing 60 self.refresh() 61 62 def refresh(self): 63 ''' 64 Redraw the displayed output with the current value of self.drawing. 65 ''' 66 self._image = self.drawing.asSvg() 67 68 def _receive_msg(self, _, content, buffers): 69 if not isinstance(content, dict): 70 return 71 name = content.get('name') 72 callbacks = { 73 'mousedown': self.mousedown_callbacks, 74 'mousemove': self.mousemove_callbacks, 75 'mouseup': self.mouseup_callbacks, 76 'timed': self.timed_callbacks, 77 }.get(name, ()) 78 try: 79 if callbacks: 80 if name == 'timed': 81 self._call_handlers(callbacks, content) 82 else: 83 self._call_handlers(callbacks, content.get('x'), 84 content.get('y'), content) 85 except BaseException as e: 86 suppress = any( 87 handler(self, e) 88 for handler in self.exception_callbacks 89 ) 90 if not suppress: 91 raise 92 finally: 93 if name == 'timed': 94 self._frame_blocked += 1 95 else: 96 self._mousemove_blocked += 1 97 98 99 def mousedown(self, handler, remove=False): 100 ''' 101 Register (or unregister) a handler for the mousedown event. 102 103 Arguments: 104 remove: If True, unregister, otherwise register. 105 ''' 106 self.on_msg 107 self._register_handler( 108 self.mousedown_callbacks, handler, remove=remove) 109 110 def mousemove(self, handler, remove=False): 111 ''' 112 Register (or unregister) a handler for the mousemove event. 113 114 Arguments: 115 remove: If True, unregister, otherwise register. 116 ''' 117 self._register_handler( 118 self.mousemove_callbacks, handler, remove=remove) 119 120 def mouseup(self, handler, remove=False): 121 ''' 122 Register (or unregister) a handler for the mouseup event. 123 124 Arguments: 125 remove: If True, unregister, otherwise register. 126 ''' 127 self._register_handler( 128 self.mouseup_callbacks, handler, remove=remove) 129 130 def timed(self, handler, remove=False): 131 ''' 132 Register (or unregister) a handler for the timed event. 133 134 Arguments: 135 remove: If True, unregister, otherwise register. 136 ''' 137 self._register_handler( 138 self.timed_callbacks, handler, remove=remove) 139 140 def on_exception(self, handler, remove=False): 141 ''' 142 Register (or unregister) a handler for exceptions in other handlers. 143 144 If any handler returns True, the exception is suppressed. 145 146 Arguments: 147 remove: If True, unregister, otherwise register. 148 ''' 149 self._register_handler( 150 self.exception_callbacks, handler, remove=remove) 151 152 def _register_handler(self, callback_list, handler, remove=False): 153 if remove: 154 callback_list.remove(handler) 155 else: 156 callback_list.append(handler) 157 158 def _call_handlers(self, callback_list, *args, **kwargs): 159 for callback in callback_list: 160 callback(self, *args, **kwargs)