Шрифты в 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)
Шрифты внутри математических формул вида '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)
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)
Нюансы
- должен быть отключён внешний 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