2

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()
Alter
  • 3,332
  • 4
  • 31
  • 56
  • I don't understand what is your question? where is `Artist`? – I'mahdi Aug 22 '21 at 12:16
  • Thanks, I'll make that clearer. `Artist` is the baseclass matplotlib uses to display items (lines, text, etc), so the artist I'm looking is the text element with the magnitude (eg. the one that turns red in the plot). – Alter Aug 22 '21 at 12:19
  • If it's useful: https://matplotlib.org/stable/api/artist_api.html – Alter Aug 22 '21 at 12:20

1 Answers1

2

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())
    
tdy
  • 36,675
  • 19
  • 86
  • 83
  • 1
    Neat! re:set_text that's good to know (and very odd). Is there a way to set the new text's position based on the old text? It seems like there's going to be some guesswork involved otherwise – Alter Aug 22 '21 at 12:48
  • @Alter see if the update makes sense and works for you. the whole thing is a bit weird – tdy Aug 22 '21 at 13:26
  • 1
    I ran into the same issue with`plt.draw()`... very weird. Didn't realize it would change again after `plt.show()`. I'm kind of surprised matplotlib can be this difficult to work with, thanks for figuring out my messy problem. – Alter Aug 22 '21 at 14:01