Python Charts

Python plotting and visualization demystified

How to Use Custom Brand Fonts (e.g., Google Fonts) in Matplotlib

Use a custom font, like one downloaded from Google Fonts, in Matplotlib charts instead of the default DejaVu Sans.

Every Matplotlib chart uses DejaVu Sans unless told otherwise, which is fine for quick exploratory plots but rarely matches a brand's actual typography. Swapping in a custom font, like one downloaded from Google Fonts, is a three-step process: get the font file, register it, tell Matplotlib to use it.

Quick Example

Download a font's .ttf file, register it with addfont(), then set it as the active font family.

import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

font_path = "PlayfairDisplay-Bold.ttf"  # downloaded from fonts.google.com
fm.fontManager.addfont(font_path)

font_name = fm.FontProperties(fname=font_path).get_name()
plt.rcParams["font.family"] = font_name

fig, ax = plt.subplots()
ax.bar(months, revenue)
ax.set_title("Custom Brand Font")

Side by side comparison of a matplotlib chart using the default DejaVu Sans font versus a custom brand font

addfont() makes the font file available to Matplotlib's font manager for the rest of the session; it doesn't apply it anywhere on its own. Setting plt.rcParams["font.family"] afterward is what actually switches every subsequent chart over to that font.

Getting the correct font name

The single most common mistake here is passing the filename, or the name shown on the Google Fonts website, into font.family. Matplotlib needs the font's internal name, which is embedded in the font file itself and doesn't always match either of those.

font_name = fm.FontProperties(fname="PlayfairDisplay-Bold.ttf").get_name()
print(font_name)  # e.g. "Playfair Display", not the filename

FontProperties(fname=...).get_name() reads that internal name directly out of the file, which is the reliable way to get the exact string font.family expects, rather than guessing at it from the filename or downloaded folder name.

Mixing a heading font with a body font

Setting font.family globally applies one font to the entire chart. For a heading font paired with a separate body font, a common brand typography pattern, pass a FontProperties object directly to individual text elements instead.

heading_font = fm.FontProperties(fname="PlayfairDisplay-Bold.ttf")
body_font = fm.FontProperties(fname="Inter-Regular.ttf")

ax.set_title("Q2 Team Performance", fontproperties=heading_font, fontsize=22)
ax.set_ylabel("Score", fontproperties=body_font, fontsize=12)

for label in ax.get_xticklabels() + ax.get_yticklabels():
    label.set_fontproperties(body_font)

Matplotlib bar chart using a bold serif font for the title and a separate sans-serif font for axis labels and ticks

fontproperties on an individual call (set_title(), set_ylabel(), ax.text()) overrides the global font.family for that one element only, which is what makes a two-font layout possible without switching the rc setting back and forth mid-chart.

Two gotchas that both look like "the font just isn't applying"

Wrong name, silent fallback. Get the font name wrong, and Matplotlib doesn't raise an error. It prints a quiet findfont warning to the console and silently falls back to the default font, which is easy to miss if you're not watching stderr.

plt.rcParams["font.family"] = "PlayfairDisplay-Bold"  # wrong: this is a filename, not the internal name
# Console: findfont: Font family 'PlayfairDisplay-Bold' not found.

Setting rcParams too late. This one is sneakier: rcParams["font.family"] only affects text created after it's set. A title or label created before the change keeps whatever font was active when it was created, even if you change rcParams and re-render the figure afterward.

fig, ax = plt.subplots()          # title/axis Text objects are created here
plt.rcParams["font.family"] = font_name   # too late for this figure
ax.set_title("My Chart")          # still picks up the family that was active
                                    # when `ax` itself was created, not this line

Comparison showing a wrong font family name silently falling back to the default font versus the correct internal name applying properly, each built as its own separate figure

The fix for the second gotcha is to set rcParams["font.family"] before calling plt.subplots() (or any other figure-creating call), not after. If two figures need two different fonts in the same script, that means building them as two fully separate plt.subplots() calls, each with the rcParam set immediately beforehand, rather than reusing one figure and changing the rcParam in between. This is also the strongest argument for preferring the fontproperties= approach from the previous section over global rcParams mutation: an explicit FontProperties object passed directly to set_title() has no creation-order dependency to get wrong.

If a custom font "isn't working" and the chart still looks like the default, check the console for a findfont warning first, and if there isn't one, check whether rcParams was set before or after the figure was created.

Practical Tips

  • Always get the font's real internal name with FontProperties(fname=path).get_name() rather than typing it in by hand; the filename and the Google Fonts display name frequently don't match it exactly.
  • addfont() only needs to run once per session (or once per script); it doesn't need to be called again before every plot.
  • Watch the console for findfont: Font family '...' not found warnings; this is Matplotlib's only signal that a font name didn't resolve, and it still renders a chart, just with the wrong font.
  • Set rcParams["font.family"] before creating the figure (plt.subplots()), not after; text elements keep whatever font was active at their own creation time, regardless of later rcParams changes.
  • Use fontproperties= on individual text calls for a two-font layout (heading vs. body); reserve plt.rcParams["font.family"] for when the whole chart should use one font.
  • Bold and italic variants are usually separate font files (e.g. Inter-Bold.ttf, Inter-Italic.ttf); register and reference each one individually rather than expecting Matplotlib to synthesize bold or italic from a single regular-weight file.

Similar Topics