Update text in matplotlib through the Artist

Viewed 187

I want to be able to freely change a matplotlib plot. I'm doing this by finding the Artist objects in a plot (which are responsible for drawing everything) but I can't update the text.

For example, here I attempt to change the magnitude text. I can update the color and position but not the text like I want to. enter image description here

import matplotlib.pyplot as plt

# Make the plot
data = [1e6 + i for i in range(10)]
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(data)
plt.title("Normal")
plt.subplot(1, 2, 2)
plt.plot(data)
plt.title("Updated Artist")
plt.draw()

# Find all `Text` Artist objects in the current axis
t = plt.gca().findobj(plt.Text)

# Filter them to get the one with the text we want
t = next(filter(lambda x: x.get_text() == '+1e6', t))

# Update the position
pos = t.get_position()
pos = (pos[0]+0.1, pos[1])
t.set_position(pos)

# Set properties
t.set_text('1,000,000') # This doesn't work! :( 
t.set_color('red')

plt.show()
1 Answers

I'm not sure if it's intended or a longstanding bug, but set_text() has not worked for the offsetText at least since 2015.

You can hide the offsetText and then add your custom label with Axes.text:

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.plot(data)
ax1.set_title('Normal')
ax2.plot(data)
ax2.set_title('Updated Artist')

ax2.yaxis.offsetText.set_visible(False)
ax2.text(-0.4, max(data)+0.6, '1,000,000', color='r', size='small')
manual sci notation label

To get the text coords programmatically, it seems some weird workarounds are needed.

Theoretically we should be able to use get_position() with get_transform(), but this always places the text at (0, 0.5):

# does not work
x, y = ax2.yaxis.offsetText.get_position()
transform = ax2.yaxis.offsetText.get_transform()
ax2.text(x, y, '1,000,000', transform=transform)

If we first force a canvas redraw, it places the text at (0, 345.952885) which is closer but still not correct:

# still does not work
plt.draw()
x, y = ax2.yaxis.offsetText.get_position()
transform = ax2.yaxis.offsetText.get_transform()
ax2.text(x, y, '1,000,000', transform=transform)

So the only way I could get this to work is:

  1. Run the plot once

  2. Get the coords manually after the plot has run once:

    ax2.yaxis.offsetText.get_position()
    
    # (0, 301.55984375)
    
  3. Go back and fill in the coords:

    ax2.text(0, 301.55984375, '1,000,000', transform=ax2.yaxis.offsetText.get_transform())
    
Related