Skip to content

[Bug]: Backgroundcolor of text is set in a too wide range #29874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
siegbert7 opened this issue Apr 6, 2025 · 7 comments
Open

[Bug]: Backgroundcolor of text is set in a too wide range #29874

siegbert7 opened this issue Apr 6, 2025 · 7 comments

Comments

@siegbert7
Copy link

Bug summary

Backgroundcolor of text is set in a too wide range when setting it for the first time.
Problem: Border of first color remains when backgroundcolor is set again.

Image

Code for reproduction

''' Backgroundcolor of text is set in a too wide range when setting it for the first time.
    Problem: Border of first color remains when backgroundcolor is set again.'''

# windows 11
# Python version 3.13.2
# Matplotlib version 3.10.1

import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, 1)
fig.set_dpi(100)

txt   = ax.text(0.1, 0.8, 'There should not be a red border around this text !')

##txt.set(backgroundcolor='none')     # Uncomment this line as workaround to avoid red border
txt.set(backgroundcolor='red')        # Set backgroundcolor for the first time
txt.set(backgroundcolor='none')       # Cannot reset backgroundcolor of text completely, red border remains

plt.show()

Actual outcome

Text with red border, see picture above.

Expected outcome

Text without red border.

Additional information

There is a workaround for the first, but not a fix, see Python program above.

Operating system

Windows

Matplotlib Version

3.10.1

Matplotlib Backend

tkagg

Python version

3.13.2

Jupyter version

Installation

pip

@timhoffm
Copy link
Member

timhoffm commented Apr 6, 2025

The background is realized through an internal FancyBboxPatch (Text._bbox_patch). It is initally None and only created if you want a background or border around the text.

What happens here is that the patch is created with finite linewidth 1 and the edgecolor is the same as the facecolor (One can debate whether that behavior is reasonable, but it likely needs a closer look to handle all potential cases reasonably). To completely undo txt.set(backgroundcolor='red') you should remove the background patch via txt.set(bbox=None) instead of making the patch transparent.

@timhoffm
Copy link
Member

timhoffm commented Apr 6, 2025

Note: The behavior is due to

self.set_bbox(dict(facecolor=color, edgecolor=color))

There is a need to do something about the edge because by default rcParams["patch.linewidth"] is 1. The alternative to setting edgecolor to the same color as facecolor would be to set linewidth=0. That might be the slightly better approach. OTOH it will lead to subsequent txt.set_bbox(edgecolor=...) have on visual effect. So that's not ideal either. Overall, I believe it's not worth changing the behavior and the improved documentation in #29875 should suffice.

@siegbert7
Copy link
Author

Thanks for your reply.

For an user of matplotlib like me it is hard to guess, that the problem of the red border results from the initialization of the text object and how to work around.
At least you should point out in the manual, that "txt.set(backgroundcolor='none')" as a command before is a simple workaround. Or something like this.

I got another problem, probably caused by the initialization of text object, which is more hard to understand for the user.
It appears when you try to get the width of the text. Sometime it works, sometimes there is an exception:

----------------------------

import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, 1)
fig.set_dpi(100)

txt = ax.text(0.1, 0.8, 'My Text ...')
try:
width = txt.get_bbox_patch().get_width() # You will get an AttributeError:
except AttributeError:
print("'NoneType' object has no attribute 'get_width'")

Make it work with the "workaround of backgroundcolor":

txt.set(backgroundcolor='none')
width = txt.get_bbox_patch().get_width() # Now you will get the width

plt.show()

@rcomer
Copy link
Member

rcomer commented Apr 6, 2025

There is a need to do something about the edge because by default rcParams["patch.linewidth"] is 1. The alternative to setting edgecolor to the same color as facecolor would be to set linewidth=0. That might be the slightly better approach. OTOH it will lead to subsequent txt.set_bbox(edgecolor=...) have on visual effect. So that's not ideal either. Overall, I believe it's not worth changing the behavior and the improved documentation in #29875 should suffice.

Could we instead set edgecolor to "none"? If I understand correctly, that is the default for a patch these days anyway (though it is black in classic mode).

@timhoffm
Copy link
Member

timhoffm commented Apr 6, 2025

Thanks, that's a good idea and probably the correct solution.

The only downside is that the size of the box changes (left half is edgecolor none):

Image

This is a breaking change. It's probably still worth doing so and communicating it as a fix.

@rcomer
Copy link
Member

rcomer commented Apr 7, 2025

Another option might be to set the edgecolor to match facecolor here

self._bbox_patch.update(dict(facecolor=color))

Then set_backgroundcolor would be the same as set_color directly on the patch

def set_color(self, c):
"""
Set both the edgecolor and the facecolor.
Parameters
----------
c : :mpltype:`color`
See Also
--------
Patch.set_facecolor, Patch.set_edgecolor
For setting the edge or face color individually.
"""
self.set_facecolor(c)
self.set_edgecolor(c)

@timhoffm
Copy link
Member

timhoffm commented Apr 7, 2025

That would be a bit counter-intuitive to me. My mental model is that the text background color is only the fill and does not affect the frame. E.g. when I have

text.set_bbox(dict(facecolor=('red'), edgecolor='black'))
text.set_backgroundcolor('green')

I would expect that the black frame stays.

I also like that the box has sharp color edges when no edge line is used. The boundary line is antialiased and blurs the edges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants