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()
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()
text() call in three coordinate systems.| Coordinates | Code | (0, 0) is | (1, 1) is |
|---|---|---|---|
| Data (default) | ax.text(x, y, s) | the data point 0, 0 | the data point 1, 1 |
| Axes | ax.text(x, y, s, transform=ax.transAxes) | bottom-left of the plot area | top-right of the plot area |
| Figure | fig.text(x, y, s) | bottom-left of the whole figure | top-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()
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()
ha/va(horizontal / vertical alignment) decide which part of the text sits at (x, y).- Write maths between
$...$in a raw string, for exampler"$\alpha^2$". - Use
\nfor 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()
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()
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
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()
TextBox widget under the plot.Related Matplotlib tutorials:
- Put the legend outside the plot
- Change the title font size
- Set the x-axis label
- Make room for text with tight_layout()
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.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn