Skip to content
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

Customize legend in object interface #3247

Closed
Auerilas opened this issue Feb 7, 2023 · 3 comments
Closed

Customize legend in object interface #3247

Auerilas opened this issue Feb 7, 2023 · 3 comments

Comments

@Auerilas
Copy link

Auerilas commented Feb 7, 2023

Hi,
Is it possible to customize the legend of an object interface? Right now I'm trying a variety of ways to access it via the matplotlib axes and figures, but nothing changes the legend.

g = (so.Plot(ange_surv, x='Week ', y='GWC',
color='Treatment ')
.add(so.Dot(alpha = 0.35), so.Jitter(), so.Dodge())
.add(so.Range(linewidth=2), so.Est(errorbar=('se', 1)), so.Dodge())
.add(so.Dot(pointsize=12), so.Agg(), so.Dodge())
.label(x='Week of Experiment', y='GWC')
.theme(sns_theme)
)
f = plt.figure()
res = g.on(f).plot()
ax = f.axes[0]
ax.legend(ncols=2, loc=2, bbox_to_anchor=(1.01, 1.01))
plt.show()

And the matplotlib rcparams don't allow for fine-tuning.

@thuiop
Copy link
Contributor

thuiop commented Feb 7, 2023

See issue #2994 but tl;dr, legends are a pain and matplotlib does not really have an interface for modifying them.

I currently use this function for moving the legend around :

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)

(it is custom made but largely inspired by move_legend)

@Auerilas
Copy link
Author

Auerilas commented Feb 7, 2023 via email

@mwaskom
Copy link
Owner

mwaskom commented Feb 8, 2023

Yep, very aware of the need for this, but want to get it right. Legends are tricky, they're basically their own plot within a plot. Going to close as a duplicate of #2994 (but thanks @thuiop for that handy recipe).

@mwaskom mwaskom closed this as not planned Won't fix, can't repro, duplicate, stale Feb 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants