Skip to content

Save button should save in rcparams format, not "png" #138

Description

@thomasaarholt

@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 of plt.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_save and handle_binary

Activity

  1. ianhi commented on Jul 24, 2020

    @ianhi
    Collaborator

    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:

    1. Button pressed -> open jupyter dialog for savefig directory -> plt.savefig

    2. Use js to download the file but use plt.savefig to generate the buffer first.

      1. js On save button being pressed send a message to python side
      2. py buf = Bytes(); plt.savefig(buf')
      3. py self.send({'name':'saving-figure'}, [buf])
      4. js on:msg use current saving infrastructure.
  2. kochjens commented on Mar 26, 2022

    @kochjens

    Hi - running into the same limitation. Are there any concrete plans to move one of the suggested solutions forward?

  3. ianhi commented on Mar 26, 2022

    @ianhi
    Collaborator

    @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

  4. kochjens commented on Mar 26, 2022

    @kochjens

    @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)?

  5. ianhi commented on Mar 27, 2022

    @ianhi
    Collaborator

    @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:

    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:

    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_img which should call savefig into a bytes object - you can copy this implementation:

    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 data dict - that is, as a sibling to the type key.

    Typescript:

    I think the only changes necessary will be contained to this function:

    ipympl/src/mpl_widget.ts

    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_binary function (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 (setting href and calling click) to look more like this SO answer: https://stackoverflow.com/a/23451803/835607

  6. kochjens commented on Mar 27, 2022

    @kochjens

    @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.

  7. ianhi commented on Mar 27, 2022

    @ianhi
    Collaborator

    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

  8. ianhi commented on Mar 27, 2022

    @ianhi
    Collaborator

    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.

  9. kochjens commented on Mar 27, 2022

    @kochjens

    @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?

  10. ianhi commented on Mar 27, 2022

    @ianhi
    Collaborator

    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

  11. added 10 commits that reference this issue on Jan 14, 2026
    7954aad
    647539b
    bed1159
    0087902
    65a12dc
    4865f33
    9f64d4d
    31e24d1
    94b59c4
    da128dc
  12. added a commit that references this issue on Jan 21, 2026
    805057a
  13. ianhi commented on Jan 21, 2026

    @ianhi
    Collaborator

    this should now work in the just released on pypi (soon on conda-forge) version 0.10.0

  14. thomasaarholt commented on Jan 21, 2026

    @thomasaarholt
    ContributorAuthor

    Great! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions