Interactive Swing & JavaFX rendering in notebooks#122
Conversation
Register a renderer for java.awt.Component that paints it off-screen into a BufferedImage and reuses the existing Image PNG pipeline. Works headless (no native window is created), so display(component) and a bare component expression both render a static snapshot.
displayInteractive(component) streams PNG frames of an AWT/Swing component over a kernel-opened comm and dispatches mouse/keyboard events back into it. Frames are coalesced (~30fps) with duplicate-frame suppression, and a custom RepaintManager streams async repaints (timers, model changes). Keyboard is delivered via KeyboardFocusManager.redispatchEvent so it is not swallowed by the focus manager off-screen. JavaFX rides the same pipeline through JFXPanel: pass a javafx.scene.Node (wrapped automatically) or a JFXPanel. JavaFX is an optional, user-supplied dependency and is handled reflectively, so jjava-jupyter has no compile/link dependency on it. A JFXPanel-containing tree is hosted in an off-screen displayable window so its input can be mapped; FX keys are injected via Event.fireEvent. The output also carries a static image/png snapshot, so frontends without the JupyterLab extension degrade gracefully to a still image. Adds an openComm(target, data, factory) overload so the comm_open carries a token.
A JupyterLab 4 extension (jupyterlab-extension/, package jjava-swing) that renders the interactive output: a mime renderer mounts a canvas for the custom MIME type and a notebook plugin registers the comm target, drawing streamed frames and forwarding mouse/keyboard events. Includes example notebooks for Swing and JavaFX. Also ignores local dev artifacts (.venv, .ipynb_checkpoints).
|
Hi Mohamad, this looks pretty cool (though still need to try it out). Yeah, maybe we should try to digest it one piece at a time, starting with the most portable JupyterLab-independent part. For instance, DFLib charts are currently rendered in Jupyter as HTML/JavaScript/CSS so I'd love to be able to create a JavaFX-based renderer that produces static images that e.g. can be displayed on GitHub, etc. |
|
Per the suggestion to digest this one piece at a time, I've split out the most portable, JupyterLab-independent part into #123 — static image rendering for For interactive displaying however, it would be necessary for an extension to be made to juypyterlab. The extension is meant to be a client side renderer which adds some events which then get send to the swing/javafx application. |
Summary
Render AWT/Swing components — and JavaFX (
Node/JFXPanel) — in notebooks, both statically and interactively.What's included
java.awt.Componentrenders as a PNG viadisplay(component)or a bare component expression — it's painted off-screen into aBufferedImageand reuses the existingImagepipeline. Headless-safe (no native window is created).displayInteractive(component)streams PNG frames of the component over a kernel-opened Jupyter comm and dispatches mouse/keyboard events back into it. Frames are coalesced (~30 fps) with duplicate-frame suppression, and a customRepaintManagerstreams asynchronous repaints (animation timers, model changes).javafx.scene.Node(auto-wrapped) or aJFXPaneltodisplayInteractive(...). JavaFX is an optional, user-supplied dependency (e.g. via%maven) and is handled reflectively, sojjava-jupyterkeeps no compile/link dependency on it.image/png, so frontends without the extension (or non-Lab frontends) show a still image instead of an error. Notebooks that never use Swing are unaffected — AWT is not initialized until a component is actually rendered.jupyterlab-extension/(packagejjava-swing): a mime renderer that mounts a<canvas>for the custom MIME type plus a notebook plugin that registers the comm target, draws streamed frames, and forwards events. Plus example notebooks for Swing and JavaFX.ActionListener, typing →JTextField, frame streaming on bind, duplicate-frame skip, and the static-fallback bundle.Design notes
KeyboardFocusManager.redispatchEvent(Swing) andEvent.fireEventinto the focused FX node (JavaFX), because synthetic key events would otherwise be swallowed off-screen (no Java window holds focus).JFXPanel-containing tree is hosted in an off-screen displayable window so its input can be coordinate-mapped.CommManager.openComm(target, data, factory)overload so the kernel-initiatedcomm_opencan carry a correlation token.Limitations
Synthetic off-screen dispatch covers custom-painted panels, charts, buttons, text fields, tables, etc., but not features needing their own native windows or the real lightweight dispatcher: tooltips, popup menus, combo-box dropdowns, drag-and-drop, some focus-traversal edge cases. JavaFX additionally needs a display (or Monocle on a headless server) and modifications to the kernel.json to tell Javafx to run headless.
Try it
Then see
jupyterlab-extension/examples/(swing-demos.ipynb,javafx-demo.ipynb).