1

I am calling sns.displot to get a FacetGrid

g = sns.displot(
    data=df_all,
    x='a_number',
    hue='a_condition',
    row='a_factor',
    col='another_factor',
    stat="density", 
    common_norm=False,
    element='step',
    palette=palette,
)

Similar to this question, I want to place the automatically-generated legend on one of the axes, instead of outside them as a figure-level legend: How to put the legend on first subplot of seaborn.FacetGrid?

Just calling g.fig.axes[row][col].legend() does not work:

No handles with labels found to put in legend.

So I should generate the handles and labels? I looked at how Grid.add_legend does this and it seems to be some magic that would require me knowing a lot more about how the class works to reproduce it (maybe I am wrong). There's also no _legend_data I can use to dynamically recreate the legend in the same way that the Grid.add_legend method does.

>>> g._legend_data

{}

The "easy" (lazy?) way would be if I could somehow copy the legend instance, add that copy to the axes I want, and then call g.fig._legend.remove() (unless anyone has any better ideas)

I can't figure out how to copy the legend and then assign it to a specific Axes.

NickleDave
  • 372
  • 2
  • 18
  • Do you care about which axes it goes in? Or is it ok if it goes in the first? If the latter, there is `facet_kws=dict(legend_out=False)`. If the former, more tricky, but there's a recipe for "moving" a legend within a single axes plot [here](https://github.com/mwaskom/seaborn/issues/2280#issuecomment-692350136) that should be straightforward to adapt to your case. – mwaskom Aug 14 '21 at 20:11
  • 1
    (Even if you want the legend to end up somewhere else, I'd probably set `legend_out=False` either way, because it will avoid the figure resize that happens to support an external legend.) – mwaskom Aug 14 '21 at 20:13
  • That comment in the GitHub issue looks exactly like what I wanted, thank you @mwaskom – NickleDave Aug 14 '21 at 21:27

1 Answers1

1

It dawned on me I could just call g.legend.set_bbox_to_anchor() as a workaround.

In this case the legend is still figure-level but it lets me get it where I want it.

edit:
@mwaskom replied with a link to this comment on a GitHub issue, that has a code snippet that does what I had in mind:
https://github.com/mwaskom/seaborn/issues/2280#issuecomment-692350136

for my purposes I tweaked it like so

def move_legend(g, ax, new_loc, **kws):
    old_legend = g.legend
    handles = old_legend.legendHandles
    labels = [t.get_text() for t in old_legend.get_texts()]
    title = old_legend.get_title().get_text()
    ax.legend(handles, labels, loc=new_loc, title=title, **kws)

move_legend(g, g.fig.axes[-2], "upper right")
g.legend.remove()

relevant discussion of what's going on under the hood is on that issue

NickleDave
  • 372
  • 2
  • 18