Skip to content
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

[Bug]: Font weight of label cannot be overwritten from rcParams when using mathtext #23860

Open
ebiyu opened this issue Sep 11, 2022 · 7 comments

Comments

@ebiyu
Copy link

ebiyu commented Sep 11, 2022

Bug summary

Font weight of x/y axis label is expected to be changed by rcParams["font.weight"] and rcParams["axes.labelweight"], and it works on first plot on the script execution. In same script, however, the updating the rcParams after first plot doesn't take effect. The problem only occurs when the label uses mathtext.

Code for reproduction

I will show two different source.
By running code below, four image will be generated.

import matplotlib.pyplot as plt

plt.rcParams["axes.labelweight"] = "normal"
plt.rcParams["font.weight"] = "normal"
plt.figure(constrained_layout=True)
plt.ylabel("$aa$")
plt.savefig("1-first.png")
plt.close()

plt.rcParams["axes.labelweight"] = "bold"
plt.rcParams["font.weight"] = "bold"
plt.figure(constrained_layout=True)
plt.ylabel("$aa$")
plt.savefig("1-second.png")
plt.close()
import matplotlib.pyplot as plt

plt.rcParams["axes.labelweight"] = "bold"
plt.rcParams["font.weight"] = "bold"
plt.figure(constrained_layout=True)
plt.ylabel("$aa$")
plt.savefig("2-first.png")
plt.close()

plt.rcParams["axes.labelweight"] = "normal"
plt.rcParams["font.weight"] = "normal"
plt.figure(constrained_layout=True)
plt.ylabel("$aa$")
plt.savefig("2-second.png")
plt.close()

Actual outcome

1-first.png
1-first

1-second.png
1-second

2-first.png
2-first

2-second.png
2-second

Expected outcome

  • 2-second.png is expected to be same as 1-first.png
  • 1-second.png is expected to be same as 2-first.png

Additional information

This is also reproduced by using plt.rcParams.update() and with plt.rc_context() instead of directly assigning to plt.rcParams[key].

Operating system

Ubuntu 20.04 on Windows subsystem for Linux

Matplotlib Version

v3.3.3, v3.5.3, HEAD (2171d95)

Matplotlib Backend

agg

Python version

3.8.11

Jupyter version

not using

Installation

pip

@ebiyu
Copy link
Author

ebiyu commented Sep 11, 2022

This bug is also reproduced on Matplotlib 3.3.3, TkAgg, Python 3.8.5 on Windows 11, macOS Monterey, Python 3.9.13, Matplotlib 3.5.1

@anntzer
Copy link
Contributor

anntzer commented Sep 11, 2022

This is the intended behavior. You can instead control the default mathtext going using mathtext.default:

#mathtext.default: it # The default font to use for math.

One reason is that there are mathtext-only font settings that can only be selected that way (e.g. mathcal).

@ebiyu
Copy link
Author

ebiyu commented Sep 11, 2022

@anntzer Thank you for your quick reply.

The point of this issue is difference by executing order even if same options are given. For example, 2-second.png and 1-first.png have same rcParams , so the output is expecrted to be same.

I think the current result is not intended behaviour. Could you give me a comment about it?

@anntzer
Copy link
Contributor

anntzer commented Sep 12, 2022

Ah, very sorry, I misread the issue. I agree something's strange here.

@anntzer anntzer reopened this Sep 12, 2022
@oscargus
Copy link
Contributor

The parameter is used here:

self.label = mtext.Text(
np.nan, np.nan,
fontsize=mpl.rcParams['axes.labelsize'],
fontweight=mpl.rcParams['axes.labelweight'],
color=mpl.rcParams['axes.labelcolor'],
)

So there is a risk that the same holds for size and color.

Maybe there is some inadvertent reuse of the Axis/Text object?

@tfpf
Copy link
Contributor

tfpf commented Sep 23, 2022

Colour and size don't appear to change when the corresponding parameters are changed after creating the figure. Only the weight. And if one displays the figure instead of saving it, the opposite happens. I was able to narrow it down to this.

import matplotlib.pyplot as plt
(fig, ax) = plt.subplots()
ax.set_xlabel("$aa$")
plt.rcParams["font.weight"] = "bold"
print(ax.xaxis.label.get_fontweight())
plt.show()
print(ax.xaxis.label.get_fontweight())

This yields
image

and

normal
normal

on standard output.

@functools.lru_cache(50)
def _parse_cached(self, s, dpi, prop):
from matplotlib.backends import backend_agg
if prop is None:
prop = FontProperties()
fontset_class = _api.check_getitem(
self._font_type_mapping, fontset=prop.get_math_fontfamily())
load_glyph_flags = {
"vector": LOAD_NO_HINTING,
"raster": backend_agg.get_hinting_flag(),
}[self._output_type]
fontset = fontset_class(prop, load_glyph_flags)
fontsize = prop.get_size_in_points()
if self._parser is None: # Cache the parser globally.
self.__class__._parser = _mathtext.Parser()
box = self._parser.parse(s, fontset, fontsize, dpi)
output = _mathtext.ship(box)
if self._output_type == "vector":
return output.to_vector()
elif self._output_type == "raster":
return output.to_raster()

I traced the code and found that when control is passed to Mathtext here, prop.get_weight() == 'normal'. But Mathtext renders the text using 'bold'-weight glyphs. Does Mathtext directly or indirectly look up plt.rcParams["font.weight"] at any point?

@anntzer
Copy link
Contributor

anntzer commented Oct 23, 2022

OK, figured this out: the patch is

diff --git i/lib/matplotlib/font_manager.py w/lib/matplotlib/font_manager.py
index a5742ef88f..b3e139397c 100644
--- i/lib/matplotlib/font_manager.py
+++ w/lib/matplotlib/font_manager.py
@@ -1342,9 +1342,11 @@ class FontManager:
         # Pass the relevant rcParams (and the font manager, as `self`) to
         # _findfont_cached so to prevent using a stale cache entry after an
         # rcParam was changed.
-        rc_params = tuple(tuple(mpl.rcParams[key]) for key in [
-            "font.serif", "font.sans-serif", "font.cursive", "font.fantasy",
-            "font.monospace"])
+        rc_params = [mpl.rcParams[f"font.{key}"] for key in [
+            "family", "style", "variant", "weight", "stretch", "size",
+            "serif", "sans-serif", "cursive", "fantasy", "monospace"]]
+        rc_params = tuple(tuple(e) if isinstance(e, list) else e
+                          for e in rc_params)  # Make this hashable.
         ret = self._findfont_cached(
             prop, fontext, directory, fallback_to_default, rebuild_if_missing,
             rc_params)

Turns out this is completely independent of mathtext and can be reproduced as follows:

import matplotlib as mpl
from matplotlib.font_manager import fontManager, findfont

mpl.rcParams["font.weight"] = "normal"
print(findfont("DejaVu Sans"))  # DejaVuSans.ttf
mpl.rcParams["font.weight"] = "bold"
print(findfont("DejaVu Sans"))  # DejaVuSans.ttf  ->  oops!
fontManager._findfont_cached.cache_clear()
print(findfont("DejaVu Sans"))  # DejaVuSans-Bold.ttf

i.e. the result of findfont depends on the default font weight/slant/etc. and these should thus also be taken into account in the caching.

Actually even that approach is slightly brittle because FontProperties are mutable (they shouldn't be, but that's another problem); best would be for findfont to just call prop.get_family()/get_style()/get_variant()/... (resolving the rcParams at the same time if needed, including expansion of the generic families (so it's a small variant over get_family() that's needed)) and pass these attributes to _findfont_cached instead of prop itself.

(Note that a similar problem also affects RendererWx.get_wx_font.)

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

No branches or pull requests

4 participants