Repository navigation
Save button should save in rcparams format, not "png" #138
Description
Activity
From #234 it's also seems that other savefig rcparams are not going to be respected. I guess the reason figure saving is structured the way it is right now is because we need javascript in order to open a download prompt. I see two potential solutions:
-
Button pressed -> open jupyter dialog for savefig directory -> plt.savefig
-
Use js to download the file but use plt.savefig to generate the buffer first.
- js On save button being pressed send a message to python side
- py
buf = Bytes(); plt.savefig(buf') - py
self.send({'name':'saving-figure'}, [buf]) - js on:msg use current saving infrastructure.
-
Hi - running into the same limitation. Are there any concrete plans to move one of the suggested solutions forward?
@jkochNU I think this issue is still looking for a champion to push it forward. Could be you :)
If anyone is interested in working on this feel free to ask questions here and i can try to help
@ianhi Happy to try, though this may turn out to be above my level of competence. I can navigate the py side, but am less familiar with the js side. Which js file is the one involved in (i) and (iv)?
@jkochNU awesome! Dev installation instructions here: https://github.com/matplotlib/ipympl#for-a-development-installation-requires-nodejs
Here are some more concrete steps to solve this
Fyi: Saving starts when a user clicks the download button which sends a message to python that originates here:
Lines 157 to 160 in 44a4ff2
this.send({ type: 'toolbar_button', name: name, }); the toolbar message handling code just forwards all message handling to the canvas so that's where the work in python will go.
Python:
Add a new branch to the
_handle_message:ipympl/ipympl/backend_nbagg.py
Lines 261 to 279 in 44a4ff2
def _handle_message(self, object, content, buffers): # Every content has a "type". if content['type'] == 'closing': self._closed = True elif content['type'] == 'initialized': # We stop syncing data url, the front-end is there and # ready to receive diffs self.syncing_data_url = False _, _, w, h = self.figure.bbox.bounds self.manager.resize(w, h) elif content['type'] == 'set_dpi_ratio': Canvas.current_dpi_ratio = content['dpi_ratio'] self.manager.handle_json(content) else: self.manager.handle_json(content) should check for
content['type']=='toolbar_message' and content['name']=='save'Then call a new method on the canvas object (named something like:
_send_savefig_imgwhich should call savefig into a bytes object - you can copy this implementation:ipympl/ipympl/backend_nbagg.py
Lines 335 to 336 in 44a4ff2
buf = io.BytesIO() self.figure.savefig(buf, format='png', dpi='figure')
then send that to frontend with something like this:self.send({'data': '{"type": "save"}'}, buffers=[data])
note: we may also need to include the filetype in this message. You can add it as a new key to the
datadict - that is, as a sibling to thetypekey.Typescript:
I think the only changes necessary will be contained to this function:
Lines 151 to 158 in 44a4ff2
handle_save() { const save = document.createElement('a'); save.href = this.offscreen_canvas.toDataURL(); save.download = this.get('_figure_label') + '.png'; document.body.appendChild(save); save.click(); document.body.removeChild(save); } You will need to change the arguments of the function to be the same as the
handle_binaryfunction(msg: any, dataviews: any)and can copy or (pull into a function) the blob extraction code from handle_binary, chanigng the mimetype as appropriate. then modify the actual downloading (settinghrefand callingclick) to look more like this SO answer: https://stackoverflow.com/a/23451803/835607@ianhi That's incredible. Thank you so much for the time and effort providing such detailed instructions. My turn to try my best implementing it.
Before getting too deep it may be worth considering if this is consistent with the other matplotlib backends: https://gitter.im/matplotlib/matplotlib?at=623fb90e3ae95a1ec1ba9692
Even if it's not, it's clearly desirable to more than one person - so I could also see either adding a new toolbar button, or making this a toggleable option.
@ianhi I have a solution with a new toolbar button ready. However, I have not done extensive testing (this needs to work on multiple platforms, multiple Python versions, multiple browsers that you all support). Is there a workflow for this that I need to follow before posting a pull request?
I have a solution with a new toolbar button ready
🎉
Is there a workflow for this that I need to follow before posting a pull request?
nope. Just push changes to a new branch on your fork and open a PR and we'll take a look
- added 10 commits that reference this issue
on Jan 14, 2026 - added a commit that references this issue
on Jan 21, 2026 this should now work in the just released on pypi (soon on conda-forge) version 0.10.0
Reacted by Thomas AarholtGreat! 😊
@tfblum noticed that the save button always saves images in the png format. In
plt.rcParams['savefig.format'], one can specify the default saving format ofplt.savefig, and it would be nice if the save button respected that.That said, I think maybe the on-screen generated image already is a png? There are two references to "png" in the codebase:
nbagg-backend,
handle_saveandhandle_binary