I am trying to right-align the entries in a matplotlib axes legend (by default they are left-aligned), but can't seem to find any way of doing this. The setup I have is below:
(I have added data and labels to my_fig axes using the ax.plot() command)
ax = my_fig.get_axes()[0]
legend_font = FontProperties(size=10)
ax.legend(prop=legend_font, num_points=1, markerscale=0.5)
There is a list of legend keyword arguments in the docs for matplotlib Axes, but there doesn't seem to be any straighforward way to set the alignment of the legend entries there. Anybody know of a backdoor way of doing this? Thanks.
EDIT:
To clarify what I am trying to achieve, right now my legend looks like:
Maneuver: 12-OCT-2011 12:00 UTC
Bias: 14-OCT-2011 06:00 UTC
I want it to look like:
Maneuver: 12-OCT-2011 12:00 UTC
Bias: 14-OCT-2011 06:00 UTC
The backdoor you're looking for is the following:
# get the width of your widest label, since every label will need
# to shift by this amount after we align to the right
shift = max([t.get_window_extent().width for t in legend.get_texts()])
for t in legend.get_texts():
t.set_ha('right') # ha is alias for horizontalalignment
t.set_position((shift,0))
I tried to get the example work, but I couldn't.
At least since matplotlib version 1.1.1 (maybe earlier) we need a dedicated renderer instance.
Take care of your backend which defines the renderer. Depending on backend the output may look fine on screen but dismal as PDF.
# get the width of your widest label, since every label will need
#to shift by this amount after we align to the right
renderer = figure.canvas.get_renderer()
shift = max([t.get_window_extent(renderer).width for t in legend.get_texts()])
for t in legend.get_texts():
t.set_ha('right') # ha is alias for horizontalalignment
t.set_position((shift,0))
The answer of #Paul Ivanov took me into the right direction. But it needed a slight adaptation for me:
max_shift = max([t.get_window_extent().width for t in legend_obj.get_texts()])
for t in legend_obj.get_texts():
t.set_ha('right') # ha is alias for horizontalalignment
temp_shift = max_shift - t.get_window_extent().width
t.set_position((temp_shift, 0))
The change means that we set a different shift for each object based on its own width and the max width of the legend text.
For those that get the Cannot get window extent w/o renderer error, add a plt.pause(0.1) :)
Related
Matplotlib has some pretty sophisticated code figuring out how to show labels, but sometimes it cramps its labels more than looks good on presentations. Is there any way to tweek it?
For example, suppose we're plotting something against date:
figure = plt.figure(figsize=(8,1))
ax = plt.gca()
ax.set_xlim(xmin=np.datetime64('2010'), xmax=np.datetime64('2020-04-01'))
We get an x-axis like this:
But supposing we want it to show more spaced years, like this:
We can kludge it in any given case by editing the labels 'mechanically'. E.g.:
ax.set_xticks([tick for i, tick in enumerate(ax.get_xticks()) if i%2==0]) # Every other year.
ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%Y"))
But that's fragile, and it breaks whenever the x limits change.
Is there any way to force more spacing in the tick setup algorithm?
Oh! Found the matplotlib source code and it led me to AutoDateLocator:
ax.xaxis.set_major_locator(matplotlib.dates.AutoDateLocator(maxticks=8))
The corresponding locator for non-dates is MaxNLocator .
I would like to stick MyText Label to the bottom right part of my figure
for a given text and a given font size (as shown on the picture for 'this is super fun', font size of '20px' and with tiny characters. I found the good position by dichotomy ).
What is the function position I need to pass to x ?
This should depends on len(MyText), text_font_size and figure width ...
from bokeh.models import ColumnDataSource, Label, LabelSet, Range1d
from bokeh.plotting import figure, output_file, show
width,height=400,300
p = figure(plot_width=width, plot_height=height)
MyText='this is super fun'
my_font_size = "20px"
labels = Label(x=width/2+25, y=0,x_units='screen', y_units='screen', text=MyText,text_font_size=my_font_size)
p.add_layout(labels)
show(p)
I don't think there is any 100% robust way to do this, actually.
You can set the text_align to "right" which helps:
p = figure(plot_width=width, plot_height=height)
labels = Label(x=width-50, y=0,
x_units="screen", y_units='screen', text_align="right",
text=MyText,text_font_size=my_font_size)
Note the -50 above is to account (roughly) for the width of the space to the right of the "plot area" (i.e where the toolbar is). However if you add a y-axis on the left side, you'd need to account for that too, and if you allow zooming, then left space can grow and shrink to accommodate bigger or smaller axis labels, which means you can't reliably account for that space with a single constant up front. You could set min_border values to be larger, which might mitigate the problem for some range of zooming/panning.
Also the above assumes the plot sizing mode is not "responsive". If the plot itself can resize then no constant value in screen units will ever work.
If you can fix your x range start/end (or add an "extra" range), then you could right-align to the range end value using "data" units. But if you allow zooming or panning then the label will move to stay fixed at that data position.
The main issue is that the "inner_width" is only computed in the browser. It's not available to the Python code because it doesn't exist outside the browser. What's really needed is some special convention or confguration to designate "inner_width" as a symbolic concept that updates to whatever is necessary, regardless of panning or zooming or resizing. I'd suggest making a GitHub issue to propose this feature.
In the mean time, I think any solution will involve some trial and error with a fixed font size in "px" and also ideally limiting panning/zooming if possible.
Matplotlib allows changing the alpha value of almost anything, but how does it work for an ticklabel?
If I have a text, it is easy:
ax.set_xticklabel(labels, alpha=alpha)
The case is different if I do not have a text as the following throws a TypeError, due to missing labels.
ax.set_xticklabel(alpha=alpha)
Therefore, my next idea was to get the automatically generated ticklabels and use them to do the job:
labels = [label.get_text() for label in ax.get_xticklabels()]
ax.set_xticklabels(labels, alpha=alpha
The problem here is, the labels are empty due to the dynamic nature of matplotlib (see here).
So, is there an easy way to change the alpha of my ticklabels without knowing the text beforehand?
It is probably not desirable to set the ticklabels themselves, if you want to change their color. The reason is that setting the labels via ax.set_ticklabels changes the formatter to a FixedFormatter; with this one would loose the automatic formatting behaviour.
Instead change the alpha of the text objects that later constitute the ticklabels. To this end plt.setp is a useful feature.
plt.setp(ax.get_xticklabels(), alpha=0.3)
The same can be achieved via
for t in ax.get_xticklabels():
t.set_alpha(0.3)
This question already has answers here:
Python Matplotlib figure title overlaps axes label when using twiny
(8 answers)
Closed 3 years ago.
I have a simple plot in matplotlib and I would like to increase the distance between the title and the plot (without using suptitle because it does not work on the version I use on a server). How to do that ?
With matplotlib 2.2+, you can use the keyword argument pad:
ax.set_title('Title', pad=20)
Adjust pad until you're happy with the axis title position. The advantage of this method over using rcParams is that it only changes this one axis title.
There doesn't seem to be a clean way to set this directly (but might be worth a feature request to add that), however the title is just a text artist, so you can reach in and change it.
#ax = plt.gca()
ttl = ax.title
ttl.set_position([.5, 1.05])
#plt.draw()
should do the trick. Tune the 1.05 to your liking.
You can just pass y parameter into plt.suptitle method:
plt.suptitle('Amazing Stats', size=16, y=1.12);
Using rcParams:
from matplotlib import rcParams
rcParams['axes.titlepad'] = 20
where 20 is the padding between the plot and the title.
From https://matplotlib.org/users/customizing.html
Another possibility is to reduce the relative size of the plot with respect to the whole figure window. In that way the distance between title and plot increases.
Before showing the plot, i.e. before plt.show(), write following command:
#The standard value of 'top' is 0.9,
#tune a lower value, e.g., 0.8
plt.subplots_adjust(top=0.8)
This method has the advantage over #CanCeylan method that the title never goes out of the figure window; because if the title is large enough, then moving it upwards through the parameter y in suptitle might move the title outside the figure. (as it happened to me ;))
In matplotlib I wish to know the cleanest and most robust means of overlaying labels onto an axis. This is probably best demonstrated with an example:
While normal axis labels/ticks are placed every 5.00 units additional labels without ticks have been overlayed onto the axis (this can be seen at 1113.75 which partially covers 1114.00 and 1105.00 which is covered entirely). The labels also have the same font and size as their normal, ticked, counterparts with the background (if any) going right up to the axis (as a tick mark would).
What is the simplest way of obtaining this effect in matplotlib?
Edit
Following on from #Ken's suggestion I have managed to obtain the effect for an existing tick/label by using ax.yaxis.get_ticklines and ax.yaxis.get_ticklabels to both remove the tick marker and change the background/font/zorder of a label. However, I am unsure how best to add a new tick/label to an axis.
In other words I am looking for a function add_tick(ax.yaxis, loc) that adds a tick at location loc and returns the tickline and ticklabel objects for me to operate on.
I haven't ever tried to do that, but I think that the Artist tutorial might be helpful for you. In particular, the last section has the following code:
for line in ax1.yaxis.get_ticklines():
# line is a Line2D instance
line.set_color('green')
line.set_markersize(25)
line.set_markeredgewidth(3)
I think that using something like line.set_markersize(0) might make the markers have size zero. The difficult part might be finding the ones that need that done. It is possible that the line.xdata or line.ydata arrays might contain enough information to isolate the ones you need. Of course, if you are manually adding the tick marks, it is possible that as you do that the instance gets returned, so you can just modify them as you create them.
The best solution I have been able to devise:
# main: axis; olocs: locations list; ocols: location colours
def overlay_labels(main, olocs, ocols):
# Append the overlay labels as ticks
main.yaxis.set_ticks(np.append(main.yaxis.get_ticklocs(), olocs))
# Perform generic formatting to /all/ ticks
# [...]
labels = reversed(main.yaxis.get_ticklabels())
markers = reversed(main.yaxis.get_ticklines()[1::2]) # RHS ticks only
glines = reversed(main.yaxis.get_gridlines())
rocols = reversed(ocols)
# Suitably format each overlay tick (colours and lines)
for label,marker,grid,colour in izip(labels, markers, glines, rocols):
label.set_color('white')
label.set_backgroundcolor(colour)
marker.set_visible(False)
grid.set_visible(False)
It is not particularly elegant but does appear to work.