Har
Har

Reputation: 3908

Adjusting the ticks to fit within the figure

I have the following matplotlib code which all it does is plots 0-20 on the x-axis vs 0-100 on the y-axis

import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(20))
ax.set_yticks(range(100))
labels = ax.set_yticklabels(range(100))

The problem I have is that not all the labels actually fit within the y-axis.

What I want to do is to divide the maximum label size into the total area of the figure to calculate how many labels I can put so that then I can group my data into sections 0-10, 10-20, 30-40 and plot those rather than display individual labels 1,2,3,4,5,6,7,8...

I am having trouble with finding the label size of the labels and the label size of the figure (correct me if I am wrong maybe it is the axis size in pixels. It is the first time I am using matplotlib)

How can I find out the number of pixels the figure/axes is taking up (width + height) and also the number of pixels (width + height) of each label?

plot

Upvotes: 7

Views: 30322

Answers (4)

SpaceMonkey55
SpaceMonkey55

Reputation: 457

This answer is valid for Matplotlib version 3.5.2

How can I find out the number of pixels the figure/axes is taking up (width + height)

Size of full figure in pixels:

f = plt.gcf()
print(f.get_size_inches() * f.dpi)

The figure also has a bbox which is the size of the figure thats printed by the __repr__ method of the figure. Did not figure out the full difference between these two, but this is how to get the size of bbox in pixels:

print(f.bbox.size)

and also the number of pixels (width + height) of each label?

The get_window_extent method can be used to get the size of bounding boxes. Therefore to get the pixel size and bottom left corner position (with X going positive rightwards and Y going positive upwards) of your axes and labels:

ax = plt.gca()
print("Axes bounding box:", ax.get_window_extent().bounds)
yticklables = ax.get_ymajorticklabels()
for i, ylb in enumerate:
     print("Y label", i, ylb.get_text(), ylb.get_window_extent().bounds)

As a dynamic solution to make tick labels appear with overcrowded tick labels, we explore two approaches:

  1. Reduce font size to make all ticks visible -> this has the drawback of having illegible tick labels for certain combination of number of ticks and figure size
  2. Skip tick labels to prevent overlap -> this has the drawback that not all Y values will have a label

For both methods we need the following piece of code (starting from OP's example):

pix2pt = 3/4
pt2pix = 1/pix2pt

get_label_bounds = lambda lab: lab.get_window_extent().bounds

bottom_label_bounds = get_label_bounds(labels[0])
top_label_bounds = get_label_bounds(labels[-1])

top_label_y = top_label_bounds[1]
top_label_height = top_label_bounds[3]
bottom_label_y = bottom_label_bounds[1]

# we add the height for top label because we get the bottom left position
ylabels_pix_length = top_label_y + top_label_height - bottom_label_y

cur_font_size = labels[0].get_size()

Reduce font size to make all ticks visible

desired_ylab_pix_length = ylabels_pix_length/len(labels)
desired_ylab_font_size = desired_ylab_pix_length*pix2pt

ax.set_yticklabels(labels, fontdict={"fontsize": desired_ylab_font_size})

Outcome with example from OP; illegible labels: Outcome with example from OP; illegible labels

Skip tick labels to prevent overlap

I think using matplotlib.ticker can accomplish the same thing as described here. It may still be desirable to use this in case its inconvenient. My usecase for this was using a seaborn heatmap with custom formatted datetime as y tick labels.

yt_pos = ax.get_yticks()
cur_font_size_pix = cur_font_size*pt2pix + 1
desired_number_of_labels = int(ylabels_pix_length/cur_font_size_pix)

label_step = int(len(labels)/desired_number_of_labels) + 1
desired_labels = labels[::label_step]
desired_label_pos = yt_pos[::label_step]

ax.set_yticks(desired_label_pos)
desired_labels = ax.set_yticklabels(desired_labels)

Outcome with skipping labels: Outcome with skipping labels

Upvotes: 3

S.A.
S.A.

Reputation: 2151

Another option is to push every other label slightly to the left. In addition to a smaller yticklabel size, this can look good:

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

# Generate some random data
labels = 'abcdefghijklmnopqrstuvwxyz'
data = np.random.random((len(labels), 100))

# Plot it, setting the ticklabel size temporarily
# this is helpful for adjusting your plot until it's right
# without having an influence on other plots outside the context
with mpl.rc_context(rc={'ytick.labelsize': 6}):
    fig, ax = plt.subplots(1, 1, figsize=(6.5,5))
    im = ax.imshow(data)

    # Plot the labels on the yaxis
    ax.set_yticks(range(len(labels)))

    # Every second label gets a bit of whitespace to the right
    # thus effectively it is pushed to the left
    labels_formatted = [label if i%2==0 else label+' '*3 for i,label in enumerate(labels)]

    ax.set_yticklabels(labels_formatted)

enter image description here

Upvotes: 2

moshik
moshik

Reputation: 1554

Simply add the following line:

plt.tight_layout()

Upvotes: 11

user6024009
user6024009

Reputation:

You could obtain the axis extents in your plot with ax.get_position(), see the documentation on axes (I would post a link, but my current reputation does not allow it).

The tick labelsize is defined in your matplotlibrc file relative to your font size. See some detail here. The default size is 12pt and can be changed with

plt.rcParams['ytick.labelsize'] = small

From the standard matplotibrc file:

Special text sizes can be defined relative to font.size, using the following values: xx-small, x-small, small, medium, large, x-large, xx-large, larger, or smaller

The easiest way to solve your problem is probably to just change the total figure size by using

fig = plt.figure(figsize=(10,30))

with the following result in your case (note that the example is a little bit extreme). If there are so many ticklabels that you have to increase the total figure size so much, you should consider decreasing the ticklabel size like shown above or decrease the number of ticklabels.

Note that this procedure involves manually adjusting the size of your total plot and/or your ticklabels until you obtain your desired output. If there is some better way to do it, I would be pleased to see it.

You can save your plot with plt.savefig(output.png) in your current working directory. If you use plt.show() and the image is larger than the popup-window, the labels will always be messed up, because the total figure is shrinked down to the window size but the label size stays constant.

I hope this answer is helpful for you.

Upvotes: 3

Related Questions