Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add support for (sub-) panel labels to Axes#15771

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
lschr wants to merge3 commits intomatplotlib:mainfromlschr:panellabels

Conversation

lschr
Copy link
Contributor

PR Summary

In scientific publications, sub-panels are often enumerated (a, b, c, …), identifying them for reference in the figure legend or main text.

This adds support for such labels, plus various options for alignment.

By default, labels are aligned with the left edge of the y axis label and with the baseline of the title.

Example:

fig, (ax1,ax2)=plt.subplots(1,2)ax1.set_title("Plot1")ax1.plot([0,1], [0,1])ax2.set_title("Plot2")ax2.plot([0,1], [0,0])ax1.set_panellabel("a")ax2.set_panellabel("b")fig.tight_layout()

ex1

However, alignment can be changed:

fig, (ax1,ax2)=plt.subplots(1,2)ax1.set_title("Plot1")ax1.plot([0,1], [0,1])ax2.set_title("Plot2")ax2.plot([0,1], [0,0])fd= {"horizontalalignment":"right"}ax1.set_panellabel("a",h_align="frame",fontdict=fd)ax2.set_panellabel("b",h_align="frame",fontdict=fd)fig.tight_layout()

ex2

It is also possible to align the labels of multiple sub-panels, even if their titles and/or axis labels are not aligned:

fig,ax=plt.subplots(2,2)ax[0,0].set_title("Plot1")axt=ax[0,0].twiny()ax[0,0].plot([0,1], [0,1])ax[0,1].set_title("Plot2")ax[0,1].plot([0,1], [0,0])ax[1,0].set_ylabel("y1")ax[1,1].set_ylabel("y2")ax[0,0].set_panellabel("a")ax[0,1].set_panellabel("b")ax[1,0].set_panellabel("c")ax[1,1].set_panellabel("d")fig.align_panellabels()fig.tight_layout()

ex3

Default font size and font weight are controlled by theaxes.panellabelsize andaxes.panellabelweight rcParams, respectively.

Before I start polishing documentation, writing unit tests, etc. I would love some feedback!

PR Checklist

  • Has Pytest style unit tests
  • Code isFlake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

orena1 reacted with thumbs up emoji
@anntzer
Copy link
Contributor

There's alreadytitle("foo", loc="left") (which can be combined with a real centered title). Would it be possible to fold the functionality here with it?

@lschr
Copy link
ContributorAuthor

lschr commentedNov 25, 2019
edited
Loading

There's alreadytitle("foo", loc="left") (which can be combined with a real centered title). Would it be possible to fold the functionality here with it?

I tried that in the beginning, but could find no way to align with the y axis label. Theoretically, one could apply the logic I implemented to the left title, but then the left title would be treated completely differently from the center and right titles. I would much rather prefer to add this new “panel label” (there may be a better name for it) to make it clear what it is intended for and that it will behave differently from the titles.

Edit:
To summarize what I found missing withtitle("foo", loc="left"):

  • it is aligned with the frame; cannot be changed to something else, like the left edge of the panel (i.e., y axis label)
  • it is not possible to align the titles of different panels, as in the last example I gave

@anntzer
Copy link
Contributor

seems fair.

@jklymak
Copy link
Member

I like the idea here. However I’d like to see a little more survey done on whether this is best practice. In particular I’m used to the a, b, c being inside the axes frame more often than not. I or aligned with the frame as@anntzer suggests. I find your examples above to be placed in a way that’s hard to read and looks strange to me so I’d like some argument that this is a good default.

@lschr
Copy link
ContributorAuthor

I am a biophysics guy and virtually all I ever see is letters in the very top left corner outside the frame.
Some examples:

The list is endless. However, this may be different in different fields of science.

Maybepreset argument would be useful.preset="topleft" could behave as it does now, while
preset="inframe" could setv_align="frame", h_align="frame", fontdict={"horizontalalignment": "left", "verticalalignment": "top"} and some sensible padding.

orena1 reacted with thumbs up emoji

@jklymak
Copy link
Member

Agreed this is consistent with how Nature and other journals do things. I am 95% convinced this is a reasonable idea. I think we may need to think about the "align" parameter and how to make it more consistent w/ the rest of matplotlib's anchoring paradigm. I'm not super comfortable with that paradigm personally, and it may not do what you need here, but maybe others should chime in:@ImportanceOfBeingErnest ?@timhoffm ?

Please do ping us if you do not get a lot of comments. This seems reasonable overall, so I think it should get some attention.

@jklymakjklymak added this to thev3.3.0 milestoneNov 28, 2019
@ImportanceOfBeingErnest
Copy link
Member

I totally see how this feature is useful. While I personally never had problems putting those labels where needed, either usingax.annotate or anAnchoredText, the correct usage of those is cumbersome and requires a good portion of knowledge. So I would consider this a nice-to-have feature.

It is definitely true that there is no canonical place for those labels (even for Journals, there is in my experience no standarized position, every one wants them at a different place it seems), and hence a solution needs to be somehow flexible - but that necessarily also makes it complicated.

Concerning the currently proposed solution I would mention that it would be nice if those labels do not slow down axes drawtime in case they are not set. Calculating a position for something that is not even shown and creating transform(s) for it seems unnecessary, and drawing the axes is pretty slow already.

Also, the interplay of this current propasal with layout managers is not too clear to me.

In general I do wonder if a flexible enough solution usingAnchoredText can be built by setting the bbox (and its transform) at drawtime.

@timhoffm
Copy link
Member

timhoffm commentedDec 1, 2019
edited
Loading

I don't have time to look into this thoroughly at the moment. So just some thought, not a full review:

  • The idea is reasonable and targets a real problem we don't have a good solution for a the moment.
  • This is similar to aligning labelshttps://matplotlib.org/devdocs/gallery/subplots_axes_and_figures/align_labels_demo.html. One should compare with that to get a consistent API.
    -h_align andv_align are conceptually different from whatalign parameters mean for single texts (but that align-relative-to-anchor concept is already a source of confusion; e.g. 14300).
  • Is it necessary to define the alignment per label, or could that simply be a parameter ofalign_panellabels?
  • Do we need dedicated panel labels, or wouldAxes.text() and a generic functionalign_texts(texts) be sufficient?

@tacaswell
Copy link
Member

I am a bit concerned about adding the API surface for this. I would rather see this sort of thing as a well-scoped helper function than as a new method.

Seehttps://gist.github.com/tacaswell/9643166 for a worked example of a (possibly too naive) function that does something similar (defaults to inside the frame) withannotate.

@jklymak
Copy link
Member

Such a helper could be a method offigure rather than axes. It’s a bit annoying having to call this for each subplot, if the labeling is the same.

@lschr
Copy link
ContributorAuthor

@ImportanceOfBeingErnest

Concerning the currently proposed solution I would mention that it would be nice if those labels do not slow down axes drawtime in case they are not set. Calculating a position for something that is not even shown and creating transform(s) for it seems unnecessary, and drawing the axes is pretty slow already.

I think that should be easy enough, like adding a check whether there is any text and whether it is
visible.

In general I do wonder if a flexible enough solution usingAnchoredText can be built by setting the bbox (and its transform) at drawtime.

I'll have a look at that. What would be the advantage over setting theText position at draw time?

@lschr
Copy link
ContributorAuthor

@timhoffm

I basically copied the API and implementation from the axis labels.

  • h_align andv_align are conceptually different from whatalign parameters mean for single texts (but that align-relative-to-anchor concept is already a source of confusion; e.g. 14300).

I agree that this is not optimal. There arehorizontalalignment andverticalalignment parameters that are passed to theText instance and these “new” parameters that specify where to put to put the labels, which mean different things. Maybeh_anchor andv_anchor would be better?

  • Is it necessary to define the alignment per label, or could that simply be a parameter ofalign_panellabels?

There are reasons not to callalign_panellabels, such as complex layouts with nested grids. Also, considering a grid with multiple rows and columns where titles are only set in the top row, it may be desirable to verticaly align the top panel labels with the titles, but all other panel labels with the frames to avoid empty spaces between panels.

For convenience however, an optional argument could be added toalign_panellabels that sets alignment for all panels.

  • Do we need dedicated panel labels, or wouldAxes.text() and a generic functionalign_texts(texts) be sufficient?

I was not able to come up with such a thing, but matplotlib internals are quite new to me. The only solution I found was to modifydraw to set the position of the label after the title and axis label positions have been set.

@lschr
Copy link
ContributorAuthor

@tacaswell

I am a bit concerned about adding the API surface for this. I would rather see this sort of thing as a well-scoped helper function than as a new method.

Seehttps://gist.github.com/tacaswell/9643166 for a worked example of a (possibly too naive) function that does something similar (defaults to inside the frame) withannotate.

It is quite easy to usetext orannotate to put a label inside the frame, but I could not find a way to align it with axis labels and titles except to set the position indraw after title and axis positions have been set. That being said, I am new to matplotlib and this may not be the best solution.

@lschr
Copy link
ContributorAuthor

@jklymak

Such a helper could be a method offigure rather than axes. It’s a bit annoying having to call this for each subplot, if the labeling is the same.

It would be easy to add a method toFigure. Something like

defset_panellabels(labels,axs=None,**kwargs):ifaxsisNone:axs=self.get_axes()forax,labinzip(axs,labels):ax.set_panellabel(lab,**kwargs)

@tacaswell
Copy link
Member

https://matplotlib.org/tutorials/text/annotations.html#using-complex-coordinates-with-annotations has some examples of how to anchor annotations against other artists.

I see the benefit of aligning the panel labels with a variety of other things, but in the scope of one axes and between the different axes, however that also brings a tremendous amount of complexity in how to specify it....

@lschr
Copy link
ContributorAuthor

@tacaswell Thanks for the hint. I guess this is what@ImportanceOfBeingErnest meant by setting the bbox at draw time. As time permits I will give it a try.

@tacaswell
Copy link
Member

This still seems like a good idea in general, but the implementation can likely be both simplified and made more robust, pushing to 3.4.0.

@lschr
Copy link
ContributorAuthor

I have had no chance to work on this as I have spent most of my time writing my PhD thesis. But as soon as the thesis is finished (quite soon, I hope) I will come back to this.

tfiers and tacaswell reacted with thumbs up emoji

@jklymakjklymak marked this pull request as draftApril 27, 2020 16:32
Lukas Schrangl added3 commitsMay 16, 2020 00:04
In scientific publications, sub-panels are often enumerated (a, b, c,…), identifying them for reference in the figure legend or main text.This adds support for such labels, plus various options for alignment.
Use Annotation instead of Text (makes padding easy), pass_get_panellabel_bbox (calculate bounding box for anchoring) method asxycoords argument.
Replace SubplotSpec.get_rows_columns by rowspan and colspan.
@lschr
Copy link
ContributorAuthor

lschr commentedMay 18, 2020
edited
Loading

I reimplemented the code that calculates the label positions according to the suggestions by@ImportanceOfBeingErnest and@tacaswell (I think), which indeed seems like a nice improvement.

@lschr
Copy link
ContributorAuthor

lschr commentedMay 18, 2020
edited
Loading

Since there were some proponents of not putting this into the Axes class, I also tried something different. I derived a PanelLabel class from Annotation

classPanelLabel(mtext.Annotation):def__init__(self,label,fontdict=None,h_align='axislabel',v_align='title',pad=0.,**kwargs):default= {'fontsize':rcParams['axes.panellabelsize'],'fontweight':rcParams['axes.panellabelweight'],'verticalalignment':'baseline','horizontalalignment':'left'}ifisinstance(pad,Number):pad= (pad,)*2super().__init__(label, (0.0,1.0),pad,xycoords=self._get_bbox,textcoords="offset points")self.update(default)iffontdictisnotNone:self.update(fontdict)self.update(kwargs)self._align= (h_align,v_align)self._align_x_grp= {self}self._align_y_grp= {self}self.set_clip_on(False)def_get_bbox(self,renderer):# Something very similar to Axes._get_panellabel_bbox        ...

which can be used like

fig,ax=plt.subplots()pl=ax.add_artist(PanelLabel("a"))

Furthermore,

defalign_panellabels(pls):forplinpls:pl._align_x_grp=set()pl._align_y_grp=set()ss=pl.axes.get_subplotspec()row0=ss.rowspan.startcol0=ss.colspan.startforplcinpls:ssc=plc.axes.get_subplotspec()ifssc.colspan.start==col0:pl._align_x_grp.add(plc)ifssc.rowspan.start==row0:pl._align_y_grp.add(plc)

allows for alignment of labels using

align_panellabels(list_of_labels)

which could probably be improved to take a list of Axes as its parameter (and then find the PanelLabel child in each Axes) and further to take a Figure as its parameter (where it could go through all associated Axes).

I think, this would be a nice solution, although not really consistent with the rest of the Axes API, where pretty much everything is done viaget_* andset_* methods.

@lschr
Copy link
ContributorAuthor

@timhoffm@tacaswell@ImportanceOfBeingErnest@jklymak Any feedback or suggestions on how to proceed?

@jklymak
Copy link
Member

Sorry this fell off everyone's radar. I think this is still a reasonable idea.

I was thinking of this having the same level as figure. i.e.figure.subplot_label(ax=axs, **kwargs), where axs is a list of axes. This is entirely analogous tofigure.colorbar. I imagine ax could default to None which would mean all the subplots in the default order.

@anntzer
Copy link
Contributor

anntzer commentedMay 17, 2023
edited
Loading

I'll close this because the feature request is being tracked at#20182, and also because I think a strategy using annotate() to get fixed offsets relative to axes corners (#25905, or the same example before that PR) would be much simpler than using groupers for alignment. Feel free to ping for reopen if you disagree.

timhoffm reacted with thumbs up emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers
No reviews
Assignees
No one assigned
Projects
None yet
Milestone
future releases
Development

Successfully merging this pull request may close these issues.

8 participants
@lschr@anntzer@jklymak@ImportanceOfBeingErnest@timhoffm@tacaswell@QuLogic@story645

[8]ページ先頭

©2009-2025 Movatter.jp