I am adding text annotations to points of a scatter plot as follows:
I do not know the length of the text annotations in advance. The figure above shows that the text annotations extend beyond the plot area.
I want to find the length of each text annotation in data coordinates to dynamically resize the plot by setting ylim to fit all the text annotations within the plot area.
I tried three solutions based on the following stack overflow answers.
I tested using two setups:
macOS Catalina - 10.15.7
Python - 3.8.14
matplotlib - 3.3.1
and
Windows 11 - 22H2
Python - 3.10.2
matplotlib - 3.5.1
import matplotlib.pyplot as plt
from matplotlib.transforms import TransformedBbox, Bbox
def get_text_object_height_1(text_obj, _ax):
# Based on
# https://stackoverflow.com/questions/24581194/matplotlib-text-bounding-box-dimensions
# to get the text bounding box we need to draw the plot
_fig = _ax.get_figure()
_fig.canvas.draw()
# get bounding box of the text in the data coordinates
bb = text_obj.get_window_extent(renderer=_fig.canvas.get_renderer())
transform = _ax.transData.inverted()
trans_box = TransformedBbox(bb, transform)
return trans_box.height
def get_text_object_height_2(text_obj, _ax):
# Based on
# https://stackoverflow.com/a/35419796/2912349
# https://stackoverflow.com/questions/58854335/how-to-label-y-ticklabels-as-group-category-in-seaborn-clustermap/58915100#58915100
# Must move the plot to see the last update. When the plot is saved, the last update is included.
# the figure needs to have been drawn once, otherwise there is no renderer
plt.ion()
plt.show()
plt.pause(1) # Can make the pause smaller. Kept it lager to see the action.
# get bounding box of the text in the data coordinates
bb = text_obj.get_window_extent(renderer=_ax.get_figure().canvas.get_renderer())
transform = _ax.transData.inverted()
trans_box = TransformedBbox(bb, transform)
plt.ioff()
return trans_box.height
def get_text_object_height_3(text_obj, _ax):
# Based on
# https://stackoverflow.com/questions/5320205/matplotlib-text-dimensions
# to get the text bounding box we need to draw the plot
_fig = _ax.get_figure()
# get text bounding box in figure coordinates
renderer = _fig.canvas.get_renderer()
bbox_text = text_obj.get_window_extent(renderer=renderer)
# transform bounding box to data coordinates
trans_box = Bbox(_ax.transData.inverted().transform(bbox_text))
return trans_box.height
x = [1, 2, 3]
y = [5, 8, 7]
labels = ['mid length', 'short', 'this is a long label']
fig, ax = plt.subplots(dpi=300, figsize=(5, 3))
ax.scatter(x=x, y=y)
y_lim_max = 0
gap = 0.3
for idx, label in enumerate(labels):
label_y_start = y[idx] + gap
txt = ax.text(x[idx], label_y_start, label, rotation='vertical', fontdict=dict(color='black', alpha=1, size=8),
transform=ax.transData)
# Can change to _2, _3 to see how other two methods work
label_length = get_text_object_height_1(txt, ax)
label_y_end = label_y_start + label_length
# Just to show the computed label length
plt.plot([x[idx], x[idx]], [label_y_start, label_y_end])
y_lim_max = label_y_end if y_lim_max < label_y_end else y_lim_max
print(f'\t{y_lim_max:8.2f}')
plt.ylim(0, y_lim_max)
plt.tight_layout()
plt.show()
# plt.savefig('scaled_plot.png')
However, the computed text annotation lengths are shorter than the text annotations. Please note that I am plotting a line beside each text annotation to demonstrate the calculated length of the respective text annotation.
Am I doing something incorrectly?
Is there a way to get the correct dimensions of the text annotations?
Is there a different approach to resizing the plot area to include all the text annotations?