Skip to content

Pass gid to renderer #15087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 24, 2019
Merged

Conversation

ImportanceOfBeingErnest
Copy link
Member

@ImportanceOfBeingErnest ImportanceOfBeingErnest commented Aug 19, 2019

PR Summary

Previously the figure's gid would not be passed to the renderer.
Fixes the issue brought up in https://stackoverflow.com/questions/57563180/fail-to-set-figure-gid-in-python-matplotlib

import matplotlib.pyplot as plt

fig = plt.figure()
fig.set_gid("myfigure")

fig.savefig("test.svg")

Previously:

<svg ....>
<g id="figure_1">

With this PR:

<svg ....>
<g id="myfigure">

Also, do the same with axis, ticks and legend and all other artists.
I did not perform a complete search on whereelse it might be missing.

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@dopplershift
Copy link
Contributor

Fix seems simple enough. Should be able to write a test that looks for a specified gid in resulting SVG output, no?

@dopplershift dopplershift added this to the v3.2.0 milestone Aug 19, 2019
@weiyx16
Copy link

weiyx16 commented Aug 19, 2019

I test it using this code:

import matplotlib.pyplot as plt

FIGURE_ID = "the_figure"
FIGURE_PATCH_ID = "the_figure_patch"

fig = plt.figure(figsize=(15, 9))
# fig.canvas.draw()

fig.set_gid(FIGURE_ID)
fig.patch.set_gid(FIGURE_PATCH_ID)

print(fig.get_gid())
print(fig.patch.get_gid())

fig.savefig('id_test.svg', format='svg')

And the output in svg file is:

 <g id="the_figure">
  <g id="the_figure_patch">

@ImportanceOfBeingErnest
Copy link
Member Author

I wrote a test and it seems almost half of the tested artists do not pass on the gid. So what will need to be done here is to systematically find all cases.

Hence marking as work in progress for now.

@ImportanceOfBeingErnest
Copy link
Member Author

Also before working more on this, can I have opinions on whether you would consider this as a bug fix or an API change (in the sense of, might we annoy people more by fixing this than we help the few that need it?)

@dopplershift
Copy link
Contributor

Does set_gid have any purpose in life than making sure it appears in the output of things like SVG? If not, then I'd say a bugfix.

@ImportanceOfBeingErnest ImportanceOfBeingErnest changed the title Pass figure gid to renderer Pass gid to renderer Aug 20, 2019
@ImportanceOfBeingErnest ImportanceOfBeingErnest force-pushed the figuregid branch 2 times, most recently from 0f98cbc to 1fcb7fc Compare August 20, 2019 11:33
@ImportanceOfBeingErnest
Copy link
Member Author

ImportanceOfBeingErnest commented Aug 20, 2019

I handled all missing gids now. The test is a bit montrous; at least it makes sure a good cross section of artists are tested.
Also still added an API change note, just to be on the safe side.

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test feels a bit cumbersome, but does serve the purpose.

buf = fd.read().decode()
fd.close()

def include(gid, obj):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps has_gid() or gid_is_set() (it's not totally clear what "include" means here)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really literally, whether or not to include an object in the test. (Not all objects in the figure actually appear in the svg, so we need to exclude them). I've added a docstring to it.


fig.canvas.draw()

counter = [0]
Copy link
Contributor

@anntzer anntzer Aug 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

counter = itertools.count() and use next(counter) in assign_gid

(or rather, enumerate() is probably enough:

for idx, obj in enumerate(fig.findobj(...)):
    gid = f"...{idx}"
    gdic[gid] = obj
    obj.set_gid(gid)

)

ax3 = fig.add_subplot(133, projection="3d")
ax3.plot([1, 2], [1, 2], [1, 2])

fig.canvas.draw()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the draw() here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I don't draw it, the ticks will not be the ones that are exported, because the ticker only creates them when drawing the figure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, of course.

fig.savefig(fd, format='svg')
fd.seek(0)
buf = fd.read().decode()
fd.close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could just write:

with BytesIO() as fd:
    fig.savefig(...)
    buf = fd.getvalue()  # no need to seek() :)

Hopefully this will resolve the code coverage issue.
@tacaswell tacaswell merged commit 5ad446d into matplotlib:master Aug 24, 2019
@tacaswell
Copy link
Member

Thanks @ImportanceOfBeingErnest !

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

Successfully merging this pull request may close these issues.

6 participants