How to Add Text to a Plot in Matplotlib (Text Box, Annotate)

To add text to a plot in Matplotlib, call plt.text(x, y, "text") or ax.text(x, y, "text"), where x and y are the position in data coordinates. Add bbox=dict(...) to put the text in a box, transform=ax.transAxes to place it relative to the axes, fig.text() to write outside the plot, and annotate() to point at a value with an arrow. This guide shows each way to add text to a plot in Python with real Matplotlib windows.

Tested with Python 3.12.5 and Matplotlib 3.11.2. The screenshots are real Matplotlib windows and the Windows Command Prompt. Reference: Axes.text, annotations guide.

Add text with plt.text()

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
sales = [120, 135, 128, 160, 172, 190]

plt.plot(months, sales, marker="o")
plt.text(3, 150, "New store opened", fontsize=12, color="darkgreen")   # x, y in data coordinates
plt.title("Monthly sales")
plt.show()
Matplotlib line chart of monthly sales with the text New store opened added at a data position with plt.text
plt.text(3, 150, ...): x = 3 is the fourth category (Apr), y = 150 on the sales axis.

With categorical x values like month names, the positions are 0, 1, 2 …, so x=3 is April.

Where the text goes: data, axes and figure coordinates

By default the position is in data coordinates and the text moves when the data or limits change. Two other coordinate systems are often more useful:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.plot(x, np.sin(x))

ax.set_ylim(-1.3, 1.6)
ax.text(8, -0.9, "data coords (8, -0.9)", ha="center")                           # moves with the data
ax.text(0.02, 0.95, "axes coords (0.02, 0.95)", transform=ax.transAxes, va="top")  # fixed corner of the axes
fig.text(0.5, 0.01, "figure coords: centred under the plot", ha="center",
         style="italic", color="gray")                                          # outside the axes
plt.subplots_adjust(bottom=0.15)
plt.show()
Matplotlib sine plot with text placed in data coordinates, in axes coordinates at the top left corner and in figure coordinates below the plot
The same text() call in three coordinate systems.
CoordinatesCode(0, 0) is(1, 1) is
Data (default)ax.text(x, y, s)the data point 0, 0the data point 1, 1
Axesax.text(x, y, s, transform=ax.transAxes)bottom-left of the plot areatop-right of the plot area
Figurefig.text(x, y, s)bottom-left of the whole figuretop-right of the whole figure

To add text outside the plot, use fig.text(), or axes coordinates beyond 0 to 1 (for example x=1.02 for the right side), and leave room with subplots_adjust() or tight_layout().

Add a text box

Pass a bbox dictionary to draw a box behind the text. Combined with axes coordinates, it makes a statistics box that stays in the corner:

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(0)
scores = rng.normal(72, 9, 200)

fig, ax = plt.subplots(figsize=(7, 4.5))
ax.hist(scores, bins=20, color="tab:blue", alpha=0.8)

stats = f"n = {scores.size}\nmean = {scores.mean():.1f}\nstd = {scores.std():.1f}"
ax.text(0.97, 0.95, stats, transform=ax.transAxes, ha="right", va="top", fontsize=11,
        bbox=dict(boxstyle="round,pad=0.5", facecolor="lightyellow", edgecolor="gray"))   # text box
ax.set_xlabel("Exam score")
plt.show()
Matplotlib histogram of exam scores with a rounded light yellow text box showing n, mean and standard deviation in the top right corner
A text box with bbox=dict(boxstyle="round", ...).

Text box styles, fonts, rotation and math

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(7, 4.5))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)

ax.text(1, 8.5, "round", bbox=dict(boxstyle="round", fc="lightblue"))
ax.text(4, 8.5, "square", bbox=dict(boxstyle="square", fc="lightgreen"))
ax.text(7, 8.5, "sawtooth", bbox=dict(boxstyle="sawtooth", fc="pink"))
ax.text(1, 5, "Bold 16 pt", fontsize=16, fontweight="bold")
ax.text(5.5, 5, "Rotated 30°", rotation=30, fontsize=13, color="purple")
ax.text(1, 2, r"Math: $E = mc^2$,  $\alpha^2 + \beta^2$", fontsize=14)
ax.text(6.5, 2, "Two\nlines", ha="center", va="center", fontsize=12,
        bbox=dict(boxstyle="circle", fc="wheat"))
plt.show()
Matplotlib examples of text box styles round, square, sawtooth and circle plus bold, rotated and math text
Box styles and text properties.
  • ha / va (horizontal / vertical alignment) decide which part of the text sits at (x, y).
  • Write maths between $...$ in a raw string, for example r"$\alpha^2$".
  • Use \n for several lines.

Annotate a point with an arrow

annotate() places text at xytext and draws an arrow to the point xy:

import matplotlib.pyplot as plt

years = [2019, 2020, 2021, 2022, 2023, 2024]
users = [1.2, 0.9, 1.8, 2.9, 3.4, 4.6]

fig, ax = plt.subplots(figsize=(7, 4.5))
ax.plot(years, users, marker="o")
ax.annotate("Pandemic dip", xy=(2020, 0.9), xytext=(2020.4, 2.6),
            arrowprops=dict(arrowstyle="->", color="red"), color="red", fontsize=11)
ax.annotate("Record year", xy=(2024, 4.6), xytext=(2022.2, 4.3),
            arrowprops=dict(arrowstyle="-|>", connectionstyle="arc3,rad=-0.3"), fontsize=11)
ax.set_ylabel("Users (millions)")
plt.show()
Matplotlib line chart of users per year with two annotations and arrows pointing to the pandemic dip and the record year
ax.annotate() with different arrow styles.

Label each point in a scatter plot

import matplotlib.pyplot as plt

cities = {"Chicago": (2.7, 227), "Houston": (2.3, 1651), "Phoenix": (1.6, 1340),
          "Boston": (0.65, 125), "Seattle": (0.75, 217)}

fig, ax = plt.subplots(figsize=(7, 4.5))
offsets = {"Seattle": (6, 6), "Boston": (6, -12)}              # nudge labels that would overlap
for name, (population, area) in cities.items():
    ax.scatter(population, area, color="tab:orange")
    ax.annotate(name, (population, area), xytext=offsets.get(name, (6, 4)),
                textcoords="offset points", fontsize=10)             # label next to each point
ax.margins(x=0.22, y=0.12)                                           # room for labels at the edges
ax.set_xlabel("Population (millions)")
ax.set_ylabel("Area (km²)")
plt.show()
Matplotlib scatter plot of five US cities with the city name written next to each point using annotate in a loop
One ax.annotate() call per point, offset a few points from the marker.

Change or remove text later

text() returns a Text object. Keep it to update or delete the text; ax.texts and fig.texts list all texts:

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
t = ax.text(0.5, 0.5, "Hello", transform=ax.transAxes, fontsize=14)
fig.text(0.5, 0.02, "footer")

print("ax.texts :", [x.get_text() for x in ax.texts])
print("fig.texts:", [x.get_text() for x in fig.texts])

t.set_text("Hello, Matplotlib")          # change an existing text later
t.set_color("red")
print("updated  :", t.get_text(), t.get_color(), t.get_fontsize())

t.remove()                               # delete it
print("after remove:", len(ax.texts), "texts on the axes")

Output:

ax.texts : ['Hello']
fig.texts: ['footer']
updated  : Hello, Matplotlib red 14.0
after remove: 0 texts on the axes
Command Prompt output listing ax.texts and fig.texts, updating a Matplotlib text with set_text and set_color and removing it
Reading, updating and removing text objects.

Interactive text input box (TextBox widget)

“Text box” can also mean an input field. Matplotlib’s TextBox widget lets the user type into the plot window; here the number typed in the box changes the frequency of the curve when Enter is pressed:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import TextBox

x = np.linspace(0, 10, 400)
fig, ax = plt.subplots(figsize=(7, 4.5))
fig.subplots_adjust(bottom=0.2)
line, = ax.plot(x, np.sin(x))
ax.set_title("frequency = 1")

def update(text):
    try:
        freq = float(text)                    # the text typed by the user
    except ValueError:
        return                                # ignore anything that is not a number
    line.set_ydata(np.sin(freq * x))
    ax.set_title(f"frequency = {freq:g}")
    fig.canvas.draw_idle()

box_ax = fig.add_axes([0.3, 0.05, 0.4, 0.075])
text_box = TextBox(box_ax, "Frequency: ", initial="1")    # an input box inside the figure
text_box.on_submit(update)                                # runs when the user presses Enter
plt.show()
Matplotlib window with a TextBox input widget labelled Frequency below a sine curve
The TextBox widget under the plot.

Related Matplotlib tutorials:

Frequently asked questions

How do I add text to a plot in Matplotlib?

Use plt.text(x, y, "text") or ax.text(x, y, "text"). The position is in data coordinates unless you pass transform=ax.transAxes.

How do I add a text box in Matplotlib?

Pass bbox=dict(boxstyle="round", facecolor="white") to text(). Use axes coordinates to keep the box in a fixed corner.

How do I add text outside the plot?

Use fig.text(x, y, "text") with figure coordinates, or axes coordinates outside 0 to 1, and make room with subplots_adjust().

What is the difference between plt.text and plt.annotate?

text() only writes text. annotate() writes text at one position and can draw an arrow to another point.

How do I put text in the corner of a plot?

ax.text(0.02, 0.98, "text", transform=ax.transAxes, va="top") places it in the top-left corner regardless of the data.

How do I change the font size of plot text?

Pass fontsize=14 (and fontweight="bold" if needed) to text(), or call set_fontsize() on the returned object.