-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
[ENH]: Rotate legend #22860
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
Comments
FYI: #16005 solves a similar problem but with another approach. Would that be sufficient for you? IMHO vertical legends only make sense with very few (or even just one?) elements. |
Thank you so much Tim! Not exactly. While I agree generally with your statement when it comes to publishing figures, I have my own reasons why I want to manipulate the legend this way. This functionality would only empower users, not necessarily encourage bad design. Simply a |
@amunozj Given the complexity of the legend artist, I would be very surprised if this were easy, however, anyone is welcome to try. |
Thank you. I was trying to apply a transformation to the bbox, but my understanding of bounding boxes and transformations is very limited :( Would something like this work? |
It would work if the children somehow inherit the parents transform But I am not sure that is the case. |
Does anyone find a good solution? Same feature request. |
It would be very nice to have this feature. My current solution is to export the figure as a pdf and edit the pdf. |
I'm using a solution based on manipulating the legend after it is drawn. Basically we can rotate and move the text based on the position we get from the handles. The X and Y position of the text in relation to the handle can be controlled through the XPad and YPad arguments; the rotation can be set to This would be much simpler and more precise if the handlers could report (x,y) coordinates from its center that could be matched to the text import matplotlib
import matplotlib.pyplot as plt
import numpy as np
def LegendVertical(Ax, Rotation=90, XPad=0, YPad=0, **LegendArgs):
if Rotation not in (90,270):
raise NotImplementedError('Rotation must be 90 or 270.')
# Extra spacing between labels is needed to fit the rotated labels;
# and since the frame will not adjust to the rotated labels, it is
# disabled by default
DefaultLoc = 'center left' if Rotation==90 else 'center right'
ArgsDefaults = dict(loc=DefaultLoc, labelspacing=4, frameon=False)
Args = {**ArgsDefaults, **LegendArgs}
Handles, Labels = Ax.get_legend_handles_labels()
if Rotation==90:
# Reverse entries
Handles, Labels = (reversed(_) for _ in (Handles, Labels))
AxLeg = Ax.legend(Handles, Labels, **Args)
LegTexts = AxLeg.get_texts()
LegHandles = AxLeg.legend_handles
for L,Leg in enumerate(LegHandles):
if type(Leg) == matplotlib.patches.Rectangle:
BBounds = np.ravel(Leg.get_bbox())
BBounds[2:] = BBounds[2:][::-1]
Leg.set_bounds(BBounds)
LegPos = (
# Ideally,
# `(BBounds[0]+(BBounds[2]/2)) - AxLeg.handletextpad`
# should be at the horizontal center of the legend patch,
# but for some reason it is not. Therefore the user will
# need to specify some padding.
(BBounds[0]+(BBounds[2]/2)) - AxLeg.handletextpad + XPad,
# Similarly, `(BBounds[1]+BBounds[3])` should be at the vertical
# top of the legend patch, but it is not.
(BBounds[1]+BBounds[3])+YPad
)
elif type(Leg) == matplotlib.lines.Line2D:
LegXY = Leg.get_xydata()[:,::-1]
Leg.set_data(*(LegXY[:,_] for _ in (0,1)))
LegPos = (
LegXY[0,0] - AxLeg.handletextpad + XPad,
max(LegXY[:,1]) + YPad
)
elif type(Leg) == matplotlib.collections.PathCollection:
LegPos = (
Leg.get_offsets()[0][0] + XPad,
Leg.get_offsets()[0][1] + YPad,
)
else:
raise NotImplementedError('Legends should contain Rectangle, Line2D or PathCollection.')
PText = LegTexts[L]
PText.set_verticalalignment('bottom')
PText.set_rotation(Rotation)
PText.set_x(LegPos[0])
PText.set_y(LegPos[1])
return(None)
Fig, Axes = plt.subplots(1, 3, constrained_layout=True)
Axes[0].bar(0, 10, label='First')
Axes[0].bar(1, 20, label='Second')
Axes[0].bar(2, 30, label='Third')
Axes[1].plot([1,2,1,2], label='First')
Axes[1].plot([3,4,3,4], label='Second')
Axes[1].plot([5,6,5,6], label='Third')
Scatters = [(np.random.uniform(-0.5,0.5,(2,10))+_).tolist() for _ in range(3)]
Labels = ('First','Second','Third')
for S,Scatter in enumerate(Scatters):
Axes[2].scatter(*Scatter, label=Labels[S])
for Ax in Axes:
Ax.set_xlim(-2,4)
LegendVertical(Ax, 90, XPad=-45, YPad=12)
# # or
# LegendVertical(Ax, 270, XPad=-45, YPad=12)
Fig.savefig('VertLeg.png')
plt.show() |
Problem
I'm trying to plot a legend that is rotated 90 degrees so that it neatly arranges along a long vertical axis I have, but there is no rotation in the legend definition. It would be great if the legend was not forced to have horizontal text and have all elements displayed horizontally.
Someone mocked what this would look like: https://i.stack.imgur.com/FqCgO.png
Proposed solution
if legend had a rotation parameter that would rotate the bbox around the anchor, it would be amazing!! Thank you so much for your hard work!
The text was updated successfully, but these errors were encountered: