For a long time I have been struggling to get blitting to work properly inside a jupyter notebook so EOmaps can be used nicely…
The 2 main problems I had so far with ipympl (e.g. %matplotlib widget) are:
- Glitches (e.g. draw events are not processed in order?)
- Event-accumulation (e.g. fast draw-events issued for example via
motion_notifycallbacks accumulate and cause tremendous lags)
Now, webagg does not suffer from the first problem, and the second problem can be addressed in webagg via a (draft) PR I’ve submitted a while ago (Avoid event accumulation in webagg backend. by raphaelquast · Pull Request #27160 · matplotlib/matplotlib · GitHub)
So now I had a look how to use webagg inside jupyter notebooks… and it seems that there is in fact a really simple way to do and it and it works just fine out of the box!
Here’s a monkey-patch example of what I have in mind:
import matplotlib.pyplot as plt
from ipywidgets import HTML, Layout
from contextlib import redirect_stdout
import io
plt.rcParams["webagg.open_in_browser"] = False
plt.switch_backend("webagg")
def show_widget(self):
if not hasattr(self, "_jupyter_widget"):
with io.StringIO() as tmp, redirect_stdout(tmp):
# Trigger webagg figure creation
plt.show()
# Obtain the adddress of the figure
webagg_address = tmp.getvalue().removeprefix("To view figure, visit ").strip()
self._jupyter_widget = HTML(
f"<iframe src={webagg_address}/{self.number} "
"style='width: 95%; height: 95%; border:none;' scrolling='no' allowtransparency='true'></iframe>"
)
# Add a callback to dynamically adjust the widget layout to fit the size of the figure
def cb(event):
self._jupyter_widget.layout.width = f"{1.1 * event.width:.0f}px"
self._jupyter_widget.layout.height = f"{(1.1 * event.height + 100):.0f}px"
self.canvas.mpl_connect("resize_event", cb)
return self._jupyter_widget
plt.Figure.show_widget = show_widget
With this, running something like
f, ax = plt.subplots()
ax.plot([1,2,3])
f.show_widget()
in a jupyter notebook will put a nice webagg based widget into the notebook ![]()
… and with EOmaps we get a nice lag-free performance inside jupyter notebooks!

Now there are a few things that are ugly in the above example and that I can’t figure out a better solution so far:
-
The only way I could imagine to figure out the actual port used by
webaggwas to capture the stdout ofplt.show()… this is of course awful… Is there a nice clean way to do this?- Side-note:
plt.rcParams["webagg.port"]is only used as a starting-point… if the port is not accessible, a random-port algorithm kicks in and another port is used… as far as I see the resulting port number is not accessible from the figure-instance…
- Side-note:
-
The size of the figure is not the size required to fit the whole “widget”… There’s a header and the toolbar etc. … Is there a way to get the actual size required to embed the whole figure?
- (right now 10% extra space (+100px for the toolbar) is used which is again ugly…)
-
Is there an easy-way to only add a few lines of css to the single-figure page ?
(in my opinion they might be nice as default in webagg as well…)those lines to be precise...
- hide the titlebar
- make the toolbar only visible on hover
- make the body transparent (so the jupyter dark-theme works as well)
div.ui-dialog-titlebar { display:none; } div.mpl-toolbar { transition:0.5s opacity; opacity: 0.0; max-height: 10px; } body:hover div.mpl-toolbar { transition: 1s opacity; opacity: 1.0; } body { background: none transparent; }
Any help / comments / suggestions on the approach would be highly appreciated!
Personally I am excited that this finally seems to do the job really well out of the box.