-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Plot legend needs more customizability #2994
Comments
Hi @mwaskom. Thank you for developing the power Plot object. Any method for adjusting legend position in the latest version? |
This comment was marked as off-topic.
This comment was marked as off-topic.
This issue is about the objects interface (where what you want actually is possible), so that comment is off topic. You may want to read through #2231 |
I have run into this limitation as well. @thuiop has a work-around written in #3247 (comment) that I will copy over since I think people might end up finding this issue looking for a temporary solution: # Credit: @thuiop
def move_legend_fig_to_ax(fig, ax, loc, bbox_to_anchor=None, **kwargs):
if fig.legends:
old_legend = fig.legends[-1]
else:
raise ValueError("Figure has no legend attached.")
old_boxes = old_legend.get_children()[0].get_children()
legend_kws = inspect.signature(mpl.legend.Legend).parameters
props = {
k: v for k, v in old_legend.properties().items() if k in legend_kws
}
props.pop("bbox_to_anchor")
title = props.pop("title")
if "title" in kwargs:
title.set_text(kwargs.pop("title"))
title_kwargs = {k: v for k, v in kwargs.items() if k.startswith("title_")}
for key, val in title_kwargs.items():
title.set(**{key[6:]: val})
kwargs.pop(key)
kwargs.setdefault("frameon", old_legend.legendPatch.get_visible())
# Remove the old legend and create the new one
props.update(kwargs)
fig.legends = []
new_legend = ax.legend(
[], [], loc=loc, bbox_to_anchor=bbox_to_anchor, **props
)
new_legend.get_children()[0].get_children().extend(old_boxes) @thuiop could you elaborate on how to use this function? I can't get it to work with a fig, ax = plt.subplots()
plot = so.Plot(...).on(ax)
plot.show() # Needed otherwise `ValueError: Figure has no legend attached.`
move_legend_fig_to_ax(fig, ax, loc="center right") # Doesn't do anything, but doesn't error |
@JeppeKlitgaard I was running into the same problem, except I was getting a plot with two legends where one was improperly placed. Here's a working example I got running that has two changes to the code you posted. The first was using plot.plot() instead of plot.show(), and the second was setting the first (and original?) legend visibility to False with fig.legends[0].set(visible=False). import matplotlib as mpl
import inspect
import seaborn as sns
import seaborn.objects as so
def move_legend_fig_to_ax(fig, ax, loc, bbox_to_anchor=None, **kwargs):
if fig.legends:
fig.legends[0].set(visible=False)
old_legend = fig.legends[-1]
else:
raise ValueError("Figure has no legend attached.")
old_boxes = old_legend.get_children()[0].get_children()
legend_kws = inspect.signature(mpl.legend.Legend).parameters
props = {
k: v for k, v in old_legend.properties().items() if k in legend_kws
}
props.pop("bbox_to_anchor")
title = props.pop("title")
if "title" in kwargs:
title.set_text(kwargs.pop("title"))
title_kwargs = {k: v for k, v in kwargs.items() if k.startswith("title_")}
for key, val in title_kwargs.items():
title.set(**{key[6:]: val})
kwargs.pop(key)
kwargs.setdefault("frameon", old_legend.legendPatch.get_visible())
# Remove the old legend and create the new one
props.update(kwargs)
fig.legends = []
new_legend = ax.legend(
[], [], loc=loc, bbox_to_anchor=bbox_to_anchor, **props
)
new_legend.get_children()[0].get_children().extend(old_boxes)
penguins = sns.load_dataset("penguins")
fig, ax = plt.subplots()
plot = (
so.Plot(penguins, x="bill_length_mm", y="bill_depth_mm",
color="species", pointsize="body_mass_g",
)
.add(so.Dot())
).on(ax)
# plot.show() # Needed otherwise `ValueError: Figure has no legend attached.`
plot.plot()
move_legend_fig_to_ax(fig, ax, loc="center", bbox_to_anchor=(0.7, 0.0, 0.9, 1)) Here are 3 figures showing the differences before and after my update to move_legend_fig_to_ax(). The first is the original code before move_legend_fig_to_ax(), the second is after running move_legend_fig_to_ax(), and the third is after my changes. Full disclosure, I don't fully understand how or why this works, but in the end I got the plot legend looking how I wanted. It seems like calling plot.show() uses pyplot as the backend renderer but calling plot.show() uses whatever you have set. In my case, I'm using a Jupyter notebook in vscode, and after a little testing, my legends show up better using plot.plot() then plot.show(). Hope this helps! |
My bad for only seeing this now @JeppeKlitgaard. This is intended to be used after plotting using |
Would be great to get this closed. Any update? |
There's not much ability to customize how the
Plot
legend appears, beyond what's available throughPlot.theme
. Desiderata include:C0
even if that doesn't appear in the plot. (This probably needs both better defaults and more customizability).The text was updated successfully, but these errors were encountered: