You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**`CanvasManagerModel`**- Headless widget that receives drawing commands as binary custom messages. Routes commands to the active canvas via `switchCanvas`.
167
-
-**`CanvasModel`**- Visual widget that renders the HTML `<canvas>` element. Subscribes to its manager's custom messages and executes drawing commands on the 2D context.
166
+
-**`CanvasManagerModel`**— Headless singleton (`_view_name: null`) that receives ALL drawing commands from Python as binary custom messages. Uses `switchCanvas` to indicate which canvas each command targets.
167
+
-**`CanvasModel`**— Visual widget that renders an HTML `<canvas>` element. Subscribes to its own comm_id and executes drawing commands on the 2D context.
168
168
169
169
Commands are serialized as binary buffers for performance. The first buffer contains JSON-encoded command metadata, and subsequent buffers carry binary data (e.g., NumPy arrays for batch operations).
170
170
171
171
<Callouttype="info">
172
-
The `hold_canvas()` context manager in Python batches many drawing commands into a single message for better performance.
172
+
The `hold_canvas()` context manager in Python batches many drawing commands into a single message for better performance. Without it, each drawing call is a separate message.
173
173
</Callout>
174
174
175
+
## Implementation Notes
176
+
177
+
Our implementation differs from ipycanvas's original frontend in how command routing works. The binary protocol and command set are identical.
178
+
179
+
### Store-level routing
180
+
181
+
In ipycanvas's original Backbone.js frontend, each `CanvasView` subscribes to the `CanvasManagerModel`'s comm channel and tracks `switchCanvas` state locally. This means every canvas sees every command for every other canvas — they just filter locally.
182
+
183
+
We route at the store level instead. `createCanvasManagerRouter` (in `canvas-manager-subscriptions.ts`) watches for `CanvasManagerModel` instances, subscribes to their messages, parses `switchCanvas` targets, and re-emits each message to only the targeted canvas's comm_id. Each `CanvasWidget` subscribes to its own comm_id and processes everything it receives — no filtering, no shared state.
184
+
185
+
```
186
+
Original ipycanvas:
187
+
Manager → broadcast to all canvases → each canvas filters locally
188
+
189
+
nteract-elements:
190
+
Manager → store router parses switchCanvas → emits to target canvas only
191
+
```
192
+
193
+
This matters for correctness: with the broadcast approach, rapid animations on one canvas can interfere with other canvases' `switchCanvas` tracking. Store-level routing eliminates this by isolating each canvas completely.
194
+
195
+
### Headless manager routing
196
+
197
+
`CanvasManagerModel` has `_view_name: null` — it's never in the widget render tree and has no visual representation. The original frontend handles this inside `CanvasView` by reaching into the manager's model. We handle it at the store level via `createCanvasManagerRouter`, which follows the same pattern as `createLinkManager` for `jslink`/`jsdlink`. This runs in `WidgetStoreProvider` and requires no component to be mounted.
198
+
199
+
### No Backbone.js
200
+
201
+
ipycanvas's original frontend uses Backbone.js models and views. We use a pure React widget store with `useSyncExternalStore` for state and store-level subscriptions for custom messages.
202
+
175
203
## Not Yet Supported
176
204
177
205
These features require cross-widget model resolution and are planned for a future release:
0 commit comments