matplotlib: alignment of legend title - matplotlib

In matplotlib, how can I adjust the alignment of the legend title? It is always centered, but I need it to be left aligned with the legend box. I tried to change the alignment of the title's Text artist which has no effect. See the following example for details:
from pylab import *
x = linspace(0, 1)
plot(x, x, label="1")
plot(x, x**2, label="2")
plot(x, x**3, label="3")
plot(x, sqrt(x), label="square root")
l = legend(title="Title", loc="best")
l.get_title().set_ha("left") # <== does not work
show()
The question boils down to what and who controls the anchor point of the title alignment? Changing the alignment to "right" or "center" has a noticeable effect. Second observation: for long title strings the anchor point moves to the left border of the legend box ...

You may align the complete legend box setting leg._legend_box.align. This aligns everything inside the legend box, but the effect is the desired one to have the title on either side of the box instead of the center.
Left aligned
leg = plt.legend(title="Title")
leg._legend_box.align = "left"
Right aligned
leg = plt.legend(title="Title")
leg._legend_box.align = "right"

I think you need to displace the Text object, using the set_position((x, y)) method. The units of x and y are pixels, so you'll have to experiment with what values look right, or use a Transform. I'm not sure off hand which combination of Transforms might be most useful.
So in short, something like this might work:
l.get_title().set_position((-10, 0)) # -10 is a guess

Related

How to move plot to the left of window in matplotlib

I have a plot with a very wide legend.
I've managed to move the legend out of the plot so that it doesn't cover it, but the legend is too wide for the window and not completely visible. This could be corrected if I knew how to move plot and legend towards the left where there is spare space.
This is what I mean:
What instruction would allow me to do this?
My current code:
f,ax=plt.subplots(1)
f.set_size_inches(14,10.5)
...
plt.legend(bbox_to_anchor=(1,1), loc="upper left")
plt.show()
Thank you
You can try adjusting the margins. The line to do that is f.subplots_adjust(left=0.05, bottom=0.07, right=0.95, top=0.95, wspace=0, hspace=0). These values can be controlled from the button to the left of the save button on the bottom toolbar. So, you can try playing with those in the gui, then when you find a value you like or that works, enter them in the suggested line of code.
Then you code should look like .
f,ax=plt.subplots(1)
f.set_size_inches(14,10.5)
...
plt.legend(bbox_to_anchor=(1,1), loc="upper left")
f.subplots_adjust(left=0.05, bottom=0.07, right=0.95, top=0.95, wspace=0, hspace=0)
plt.show()

How to remove the whitespace between the tip of the arrow of a FancyArrowPatch and the target points?

When doing an image which puts lots of focus on some arrows, I noticed that the arrows didn't reach the full length between the specified points.
For example:
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots()
fig.set_size_inches(5, 5)
w = 0.001
h = 1000
ax.set_xlim(0, w)
ax.set_ylim(0, h)
ax.add_artist(mpl.patches.FancyArrowPatch(
(w*1/3, h*0.0),
(w*1/3, h*1.0),
edgecolor='black',
facecolor='red',
arrowstyle=mpl.patches.ArrowStyle.CurveFilledAB(head_length=10, head_width=10)
))
ax.add_artist(mpl.patches.FancyArrowPatch(
(w*2/3, h*0.0),
(w*2/3, h*1.0),
edgecolor='black',
facecolor='red',
arrowstyle=mpl.patches.ArrowStyle.Simple(head_length=10, head_width=10, tail_width=1)
))
plt.savefig(
'main.svg',
format='svg',
bbox_inches='tight',
dpi=100,
)
produces:
and here is a screenshot of a browser zoom of the arrows showing that they don't touch the black line above:
How to make them touch exactly without that white space?
Tested on matplotlib==3.2.2.
Related questions:
Alter the width of an annotate arrow in matplotlib
Matplotlib - set pad between arrow and text in annotate function
shrinkA=0 and shrinkB=0
This is the first main direct reason why arrows don't touch.
Those values default to 2, presumably because arrows were originally mostly used in annotations where some space is desired.
If I set them to zero:
FancyArrowPatch(
shrinkA=0,
shrinkB=0,
then the arrows touch as shown at:
A is for the start, and B is for the end of the arrow.
The effect of linewidth
Another thing to keep in mind however is that the linewidth can also affect if the arrow touches something or not.
For example, if I add an exaggerated linewidth=10 to the original code:
FancyArrowPatch(
shrinkA=0,
shrinkB=0,
linewidth=10,
then the result is:
so we note how:
the tracing of the edges is always rounded, and therefore the arrows stop being pointy and become rounded with large line widths
Simple and CurveFilledAB have different drawing algorithms: the one for Simple makes the arrow overrun the target, and CurveFilledAB makes it under run
the linewidth above was so fat that it hid the red inner color
For the case of Simple, if you don't need a different border color, you can get back the pointy arrow with linewidth=0, and control the main arrow line width with tail_width.
This does not work for CurveFilledAB however: the drawing algorithm for that style is to use the border alone for the main arrow line, so I'm not sure how to separately control the main line width of a double headed arrow without getting the rounded corners, except for drawing two simple arrows which is uglier: matplotlib simple and two head arrows Seems like something that could be patched by adding a new SimpleAB class.
The result of:
FancyArrowPatch(
shrinkA=0,
shrinkB=0,
linewidth=0,
is:

TramineR legend position and axis

I'm working with TraMineR and I don't know how to arrange my plot. So basically what i would like to have the legend under the plot and to remove the space between the x and y axis. Any help is welcomed.
The plot:
Sample code:
seqdplot(Activities.seq, with.legend=FALSE)
legend("bottom", legend=attr(Activities.seq, "labels"),
fill=attr(Activities.seq, "cpal"),
inset=-.1, bty="o", xpd=NA, cex=.75,ncol=3)
The family of seqplot functions offers a series of arguments to control the legend as well as the axes. Look at the help page of seqplot (and of plot.stslist.statd for specific seqdplot parameters).
For instance, you can suppress the x-axis with axes=FALSE, and the y-axis with yaxis=FALSE.
To print the legend you can let seqdplot display it automatically using the default with.legend=TRUE option and control it with for examples cex.legend for the font size, ltext for the text. You can also use the ncol argument to set the number of columns in the legend.
The seqplot functions use by default layout to organize the graphic area between the plots and the legend. If you need more fine tuning (e.g. to change the default par(mar=c(5.1,4.1,4.1,2.1)) margins around the plot and the legend), you should create separately the plot(s) and the legend and then organize them yourself using e.g. layout or par(mfrow=...). In that case, the separate graphics should be created by setting with.legend=FALSE, which prevents the display of the legend and disables the automatic use of layout.
The color legend is easiest obtained with seqlegend.
I illustrate with the mvad data that ships with TraMineR. First the default plot with the legend. Note the use of border=NA to suppress the too many vertical black lines.
library(TraMineR)
data(mvad)
mvad.scode <- c("EM", "FE", "HE", "JL", "SC", "TR")
mvad.seq <- seqdef(mvad, 17:86,
states = mvad.scode,
xtstep = 6)
# Default plot with the legend,
seqdplot(mvad.seq, border=NA)
Now, we suppress the x and y axes and modify the display of the legend
seqdplot(mvad.seq, border=NA,
axes=FALSE, yaxis=FALSE, ylab="",
cex.legend=1.3, ncol=6, legend.prop=.11)
Here is how you can control the space between the plot and the x and y axes
seqdplot(mvad.seq, border=NA, yaxis=FALSE, xaxis=FALSE, with.legend=FALSE)
axis(2, line=-1)
axis(1, line=0)
Creating the legend separately and reducing the left, top, and right margins around the legend
op <- par(mar=c(5.1,0.1,0.1,0.1))
seqlegend(mvad.seq, ncol=2, cex=2)
par(op)

matplotlib: align figure legend with suptitle (bottom of text, vertically aligned)

I have a subplot with an external, horizontal legend (figure legend), and super title (suptitle). I would like to align them on the bottom of the text, vertically. I set the vertical position of the title with fig.suptitle('title', ha='left', y=placement). When I try to set the legend's location with fig.legend(handles, labels, loc=(0.65, placement)), It is not aligned. I suspect this is an issue with the point the objects are aligned on and that the legend has a border... so I changed the padding of the legend to 0, but still have to tweak it.
How could I explicitly set the anchor point, or node, of the suptitle and legend?
EDIT
Image attached below. I believe I have solved the problem with setting the suptitle parameter va='bottom, and legend parameters borderpad=0.0, borderaxespad=0. Now, if I do have a border, how would I do this? I would also like to know how to explicitly set the anchor point of the legend.
SOLUTION
When you set the bbox_to_anchor parameter in the legend, it basically changed where the legend is clipped to. If the bbox has no height or width (2-tuple), it is a point. The loc parameter is then the point on the legend frame that is anchored to the bbox. When loc is used on it's on, the bbox is determined by MPL.
Let's say I wanted the title on the left, legend on the right (with no box), and horizontally aligned at the bottom. Mmy solution would look like:
fig.suptitle('title', ha='left', va='bottom', y=placement, x=0.02)
fig.legend(handles, labels,
loc='lower right',
bbox_to_anchor=(0.98, placement),
borderpad=0.0,
borderaxespad=0,
ncol=3,
)

How to Superimpose on Matplotlib

I want to draw a triangle with two points inside using matplotlib. Here is the code I'm using:
plt.figure()
triangleEdges = np.array([[0,0],[1,0],[0.5,0.5*np.sqrt(3)]])
colors = ['red', 'green', 'blue']
t1 = plt.Polygon(triangleEdges, facecolor="none",
edgecolor='black', linewidth=2)
t1.set_facecolor('xkcd:salmon')
plt.gca().add_patch(t1)
drawSoftmaxPoint('blue',100,np.array([0.2,0.1,0.7]) )
drawSoftmaxPoint('red',100,np.array([0.5,0.1,0.7]))
plt.show()
Picture
According to the code, there should be two points inside the triangle, but it looks like the background is covering them. How can I make them visible?
Thank you!
you could use alpha and z-order in your polygon to make it happen (from the doc of matplotlib). just try to set the alpha value between 0 and 1 to check if you can see your points. and then maybe use z-order on your different elements to make sure the fill of the polygon is deepest (most behind). example of zorder:
https://matplotlib.org/gallery/misc/zorder_demo.html