Шрифты в matplotlib.pyplot
Задача: в формулах на рисунках, сделанных в matplotlib, хочется иметь шрифты, максимально похожие на шрифты основного текста (в частности - шрифты с засечками).
Подзадача: разобраться с настройкой шрифтов.
Дано: Windows 10, Jupyter notebook в Anaconda, Python 3.6, matplotlib 3.0.3.
TL;DR
matplotlib.rcParams['mathtext.fontset'] = 'dejavuserif'
Описание проблемы
По умолчанию шрифты на рисунках без засечек (семейство sans-serif), а в основном тексте документа (Latex) - с засечками (serif).
Попытка установить шрифт стандартным способом (matplotlib.rcParams[...], см. ниже) не действует на формулы, создавая впечатление, как будто шрифты "не подгружаются".
Похожие проблемы в интернете:
- нельзя установить
serif- в ответах предложили переустановить другую версию matplotlib, - аналогичная проблема с базовыми шрифтами - даже сделан баг-репорт.
Решение - установить
msttcorefonts(черезapt-get, т.е. не для Windows).
На самом деле всё решается намного проще: RTFM. Само решение в конце этой заметки.
Отступление: шрифты в matplotlib
Список доступных шрифтов выявляет модуль font_manager.
import matplotlib.font_manager
# 'ttf' option to show files with TrueType Fonts only
matplotlib.font_manager.findSystemFonts(fontpaths=None, fontext='ttf')
Можно попытаться найти (отфильтровать) искомый шрифт командой
prop - строка (в не совсем очевидном формате) или объект FontProperties.
Можно работать с текстовой строкой с помощью модуля matplotlib.fontconfig_pattern.
prop = matplotlib.font_manager.FontProperties(family='serif')
matplotlib.font_manager.findfont(prop, fontext='ttf')
В результате был выдан один шрифт: DejaVuSerif.ttf, то есть вроде наличие шрифта с засечками есть.
Имена шрифтов (не имена файлов!) можно выяснить опосредованно, зная путь к файлу шрифта
flist = matplotlib.font_manager.findSystemFonts()
names = [matplotlib.font_manager.FontProperties(fname=fname).get_name() for fname in flist]
Список найденных шрифтов хранится в файле ~/.matplotlib/fontlist-v300.json,
Точный путь к папке кеширования:
matplotlib.font_manager.get_cachedir()
Можно встретить совет удалить папку .matplotlib, но это не помогает решить проблему.
Основы шрифтовых настроек matplotlib
Хранятся или изменяются либо через объект matplotlib.rcParams (напр. rcParams['lines.linewidth'] = 2),
либо командой rc('lines', linewidth=2, color='r'). Последняя эквивалентна двум командам формата rcParams[...] = ....
В описании matplotlib.rc как раз есть пример с выбором шрифта.
Сброс настроек matplotlib: matplotlib.rcdefaults().
Решение
Возвращаемся к задаче выбора шрифта в формуле.
Обычно тип шрифта, используемый в командах matplotlib настраивается установкой rcParams['font.family'] и
rcParams['font.<font_family>'] = <список шрифтов> (список нужен для того, чтобы сразу перечислить "запасные" шрифты).
Это не сработает:
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.figure(figsize=(8,1.2))
mpl.rcdefaults() # reset font properties
plt.text(0.05, 0.7, 'a) Default fonts for all: Text with $For = m^{ul}(a) \\rightarrow \\max_a$', fontsize=14)
mpl.rcParams['font.family'] = 'serif'
plt.text(0.05, 0.05, 'b) Incomplete attempt to set a serif font everywhere:\n '
r'Text with $For = m^{ul}(a) \rightarrow \max_a$', fontsize=14)
![rcParams['font.family'] fails to set font in math mode](/assets/2019-05-28-matplotlib-fonts%5Ca-b.png)
Шрифты внутри математических формул вида 'Text $x^2$' определяются другим параметром в rcParams, а именно 'mathtext' .
Соответствующая секция присутствует в описании-примере конфигурационного файла,
также эта особенность нашлась в объяснении "бага" Matplotlib - там даже предлагалось изменить документацию matplotlib.
Это же решение нашлось явно в самой документации, в разделе математического режима Matplotlib.
Согласно ей (и примеру) ключевой параметр rcParams[mathtext.fontset] может принимать ограниченный ряд значений 'dejavusans', 'dejavuserif', 'cm' (Computer Modern), 'stix', 'stixsans' or 'custom'.
Я сначала использовал шрифт cm - Сomputer Modern, основной в Latex.
import matplotlib
matplotlib.rcParams['mathtext.fontset'] = 'cm'
Однако его размер не совпал с размером шрифта в тексте (пример с)), пришлось поменять на 'dejavuserif' (пример d)), т.к. matplotlib сам не обнаружил шрифт Computer Modern, а искать его вручную я не стал.
plt.figure(figsize=(8,0.7))
mpl.rcdefaults() # reset font properties
# and set text and math font to serif ones
mpl.rcParams['font.family'] = 'serif'
mpl.rcParams['mathtext.fontset'] = 'cm'
plt.text(0.05, 0.4, 'с) Serif fonts everywhere: Text with $For = m^{ul}(a) \\rightarrow \\max_a$', fontsize=14)
![mpl.rcParams['mathtext.fontset'] = 'cm' size mismatch](/assets/2019-05-28-matplotlib-fonts%5Cc.png)
plt.figure(figsize=(8,0.7))
# Make text font match math font
mpl.rcParams['font.family'] = 'serif'
mpl.rcParams['font.serif'] = 'DejaVu Serif'
mpl.rcParams['mathtext.fontset'] = 'dejavuserif'
plt.text(0.05, 0.4, 'd) Dejavu Serif for all: Text with $For = m^{ul}(a) \\rightarrow \max_a$', fontsize=14)
![mpl.rcParams['mathtext.fontset'] = 'dejavuserif' matches 'DejaVu Serif' font](/assets/2019-05-28-matplotlib-fonts%5Cd.png)
Нюансы
- должен быть отключён внешний Latex-интерпретатор:
rcParams['text.usetex'] = false. Настройка'mathtext.fontset'влияет на собственный Latex-интерпретаторmatplotlib. - набор доступных шрифтов для формул очень ограничен, и им может не найтись соответствия среди установленных шрифтов (у меня совпал только вариант
'dejavuserif'для формул и'DejaVu Serif'для текста). - эту настройку можно сделать только с помощью
rcParams[...]илиrc(...), и нельзя передать как аргумент (наподобиеfontdict) в функцию отрисовки (plot,text...). По этому поводу вmatplotlibоткрыт баг. Возможно, по этой причине возникают следующие особенности:- нельзя настроить разные стили (шрифты) математического текста на одной картинке,
- в jupyter notebook, к примеру, можно установить математический шрифт после вывода (
text(...)), но до отрисовкиplt.show(). Примерыe), f), g).
- размеры шрифтов в обычном тексте и формулах могут различаться (пример
c)выше).
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.figure(figsize=(8,2.5))
mpl.rcdefaults() # reset font properties
# try to set a sans (non-serif) font
mpl.rcParams['mathtext.fontset'] = 'dejavusans'
plt.text(0.05, 0.8, 'e) This shall be a sans font: Text with $For = m^{ul}(a) \\rightarrow \max_a$', fontsize=14)
mpl.rcParams['font.family'] = 'serif'
plt.text(0.05, 0.4, 'f) Here the font shall be as in b) case:\n '
r'Text with $For = m^{ul}(a) \rightarrow \max_a$', fontsize=14)
# let's set math font to a serif one
mpl.rcParams['mathtext.fontset'] = 'cm'
plt.text(0.05, 0.2, 'g) Change math formulae to serif only now. WTF?', fontsize=14)
plt.text(0.05, 0.04, 'Now all formulae appears to be serif... ($x^2 = 42$)', fontsize=14)
plt.show()

Выводы
- Подберите нужный шрифт для формул (один из 5), соответствующий одному из установленных шрифтов (напр.
cmиComputer ModernилиdejavusansиDejavu Sansи т.п.) - Используйте
mpl.rcParams['mathtext.fontset'] = ...для настройки шрифта в формулах.
Ссылки
-
https://matplotlib.org/users/customizing#a-sample-matplotlibrc-file - пример файла настроек
matplotlibс комментариями и объяснениями -
https://matplotlib.org/tutorials/text/mathtext.html - описание обработки в
matplotlibформул в Latex-стиле -
http://jonathansoma.com/lede/data-studio/matplotlib/changing-fonts-in-matplotlib/ - шрифты можно изменять в любом текстовом объекте независимо
-
https://jenyay.net/Matplotlib/RcParams - о параметрах
matplotlibв цикле статей на русском языке -
https://pythonmatplotlibtips.blogspot.com/2018/01/try-using-all-mathtext-fontset-in-python-matplotlib.html - примеры всех шрифтов математического режима
matplotlib