Adding Tables to Plots

Reviewed & published by Brayan K

A Matplotlib table renders a grid of values right inside a figure, letting you pair exact numbers with a chart so a dashboard shows both the visual trend and the precise data behind it.

Part of the free Matplotlib course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll build tables with ax.table(), add row and column labels, style individual cells, and place a table neatly below a chart.

What You'll Learn in This Lesson

1 Your First Table

ax.table() turns a list of rows into a grid. Pass cellText — a list where each inner list is one row of strings — plus colLabels for the headers and a loc for placement. Because the table fills the axes, you usually hide the axes themselves with ax.axis('off').

import matplotlib.pyplot as plt

# Each inner list is one row of the table
rows = [
    ["Latte", "90", "$4.50"],
    ["Espresso", "65", "$3.00"],
    ["Mocha", "45", "$4.75"],
    ["Tea", "30", "$2.50"],
]

fig, ax = plt.subplots()
ax.axis("off")   # hide the plotting axes; the table is the content

table = ax.table(cellText=rows,
                 colLabels=["Drink", "Cups", "Price"],
                 loc="center")
table.scale(1, 1.6)   # make the rows a bit taller

ax.set_title("Cafe Sales", pad=20)

plt.show()
The chart this code draws: Render a grid of values with ax.table()
What this code draws, rendered with Matplotlib.

What you'll see: a clean three-column table of cafe sales — Drink, Cups, and Price — with a bold header row and four data rows, sitting centered in the figure with no axes around it.

2 Row Labels & Styled Cells

Add rowLabels for a header down the left side and tint headers with colColours/rowColours. To restyle one cell, fetch the cell map with table.get_celld() (keyed by (row, col), where row 0 is the header) and call set_facecolor() on the cell you want to highlight.

import matplotlib.pyplot as plt

data = [
    ["120", "150", "170"],
    ["90", "110", "130"],
    ["60", "75", "95"],
]
cols = ["Q1", "Q2", "Q3"]
rows = ["North", "South", "West"]

fig, ax = plt.subplots()
ax.axis("off")

table = ax.table(cellText=data,
                 colLabels=cols,
                 rowLabels=rows,
                 colColours=["#cfe8ff"] * 3,
                 rowColours=["#ffe0cc"] * 3,
                 loc="center")
table.scale(1, 1.7)

# Highlight the single biggest value (row 1, col 3 -> West Q3? no: North Q3)
cells = table.get_celld()
cells[(1, 2)].set_facecolor("#b6f5c3")   # data row 1, last column

ax.set_title("Regional Sales by Quarter", pad=20)

plt.show()
The chart this code draws: Add row/column labels and recolor a single cell
What this code draws, rendered with Matplotlib.

3 A Table Below a Chart

The dashboard classic: a chart on top and its data table underneath. Plot the chart, add the table with loc='bottom', hide the x tick labels so they do not clash with the table headers, and call plt.subplots_adjust(bottom=...) to make room.

import matplotlib.pyplot as plt
import numpy as np

months = ["Jan", "Feb", "Mar", "Apr"]
revenue = [120, 150, 130, 170]
costs = [80, 90, 85, 100]

x = np.arange(len(months))

fig, ax = plt.subplots()
ax.bar(x - 0.2, revenue, 0.4, label="Revenue")
ax.bar(x + 0.2, costs, 0.4, label="Costs")
ax.set_ylabel("$k")
ax.set_title("Monthly Revenue vs Costs")
ax.legend()

# Hide x labels so the table can carry them
ax.set_xticks([])

# The table underneath, sharing the columns with the bars
cell_text = [revenue, costs]
ax.table(cellText=[[str(v) for v in row] for row in cell_text],
         rowLabels=["Revenue", "Costs"],
         colLabels=months,
         loc="bottom")

# Make room at the bottom for the table
plt.subplots_adjust(left=0.2, bottom=0.2)

plt.show()
The chart this code draws: Place a data table directly beneath a bar chart
What this code draws, rendered with Matplotlib.

What you'll see: a grouped bar chart of revenue versus costs with a tidy two-row table directly underneath showing the exact monthly figures — a compact dashboard that pairs the visual with the numbers.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to render a simple two-column table.

import matplotlib.pyplot as plt

rows = [
    ["Mercury", "0.39"],
    ["Earth", "1.00"],
    ["Mars", "1.52"],
]

fig, ax = plt.subplots()
ax.axis("off")

# Build the table with headers and center it
ax.___(cellText=rows,
       colLabels=["Planet", "AU from Sun"],
       loc="___")

ax.set_title("Distances", pad=20)

# Display the figure
plt.___()

📋 Common Errors & Quick Reference

❌ TypeError: must be str, not int

Cell values have to be strings. ✅ Convert with str(v) or a list comprehension: [[str(v) for v in row] for row in data].

❌ The bottom table is cut off

There is no room below the axes. ✅ Call plt.subplots_adjust(bottom=0.2) (and left= if you use rowLabels).

❌ colLabels length does not match rows

Every row needs the same number of columns as there are colLabels. ✅ Make all rows the same width.

Argument / CallWhat It Does
cellText=rowsThe grid of cell strings
colLabels / rowLabelsHeader cells across / down
loc='bottom'Where the table sits
table.scale(w, h)Resizes cells
table.get_celld()[(r,c)]Access one cell to style it

🎯 Mini-Challenge: A Scorecard

Build a table of three players' scores and color the header row gold.

import matplotlib.pyplot as plt

players = [
    ["Ana", "42", "Win"],
    ["Ben", "37", "Win"],
    ["Cara", "29", "Loss"],
]

fig, ax = plt.subplots()
ax.axis("off")

table = ax.table(cellText=players,
                 colLabels=["Player", "Score", "Result"],
                 loc="center")
table.scale(1, 1.8)

# Color the header row gold
for col in range(3):
    table.get_celld()[(0, col)].set_facecolor("gold")

ax.set_title("Match Scorecard", pad=20)

plt.show()
The chart this code draws: A scorecard table with a gold header row
What this code draws, rendered with Matplotlib.
import matplotlib.pyplot as plt

rows = [["Mon", 12, 4], ["Tue", 15, 6], ["Wed", 9, 2]]
cols = ["Day", "Sold", "Returned"]

fig, ax = plt.subplots()
ax.axis("off")
tbl = ax.table(cellText=[[str(c) for c in r] for r in rows], colLabels=cols, loc="center")
tbl.auto_set_font_size(False)
tbl.set_fontsize(10)

cells = tbl.get_celld()
print("Cells including the header row:", len(cells))
print("Rows of data:", len(rows), "columns:", len(cols))
print("Header cell (0,0):", cells[(0, 0)].get_text().get_text())
print("First data cell (1,0):", cells[(1, 0)].get_text().get_text())
print("Total sold:", sum(r[1] for r in rows))
print("Axis is hidden so only the table shows:", not ax.axison)

# ✅ Expected output:
# Cells including the header row: 12
# Rows of data: 3 columns: 3
# Header cell (0,0): Day
# First data cell (1,0): Mon
# Total sold: 36
# Axis is hidden so only the table shows: True
The chart this code draws: Cells counted including the header row, and one read back by its (row, column) key
What this code draws, rendered with Matplotlib.

❓ Frequently Asked Questions

Lesson complete — your figures can show the numbers!

You built tables with ax.table, added row and column labels, recolored individual cells, and placed a data table beneath a chart for a dashboard look.

Practice quiz

Which argument supplies the grid of cell values to ax.table()?

  • data=
  • values=
  • rows=
  • cellText=

Answer: cellText=. cellText is a list of rows, each a list of strings, holding the table's cell values.

What type must every value in cellText be?

  • A string
  • An integer
  • A float
  • A NumPy array

Answer: A string. Every cell value must be a string; convert numbers with str() or an f-string.

Which argument adds header cells across the top of the table?

  • rowLabels
  • colLabels
  • headers
  • topLabels

Answer: colLabels. colLabels are the header cells across the top, one per column.

How do you place a table directly under a chart?

  • loc='above'
  • loc='center'
  • loc='bottom'
  • loc='under'

Answer: loc='bottom'. loc='bottom' positions the table beneath the axes.

What does table.get_celld() return?

  • A list of column names
  • The total cell count
  • The figure object
  • A dict of cells keyed by (row, col)

Answer: A dict of cells keyed by (row, col). get_celld() returns a dict keyed by (row, col) where row 0 is the header.

In get_celld(), which key is the FIRST data row, first column?

  • (1, 0)
  • (0, 0)
  • (0, 1)
  • (2, 1)

Answer: (1, 0). Row 0 is the header, so the first data row starts at (1, 0).

Which argument adds header cells down the LEFT side?

  • sideLabels
  • rowLabels
  • leftLabels
  • indexLabels

Answer: rowLabels. rowLabels are the header cells down the left side, one per row.

How do you make the table's rows taller?

  • table.height(2)
  • table.resize(2)
  • table.scale(1, 1.6)
  • table.stretch(1.6)

Answer: table.scale(1, 1.6). table.scale(width, height) scales the cells; a height > 1 makes rows taller.

How do you recolor a single cell's background?

  • cell.color('red')
  • cell.fill('red')
  • cell.bg('red')
  • cell.set_facecolor('red')

Answer: cell.set_facecolor('red'). Call set_facecolor() on the Cell you fetched from get_celld().

Why call plt.subplots_adjust(bottom=0.2) with a bottom table?

  • To make room so the table is not cut off
  • To rotate the table
  • To delete the chart
  • To change the colormap

Answer: To make room so the table is not cut off. It adds space below the axes so the loc='bottom' table fits.

Continue this course

Frequently asked questions

How do I add a data table to a Matplotlib figure?

Call ax.table(cellText=rows, colLabels=headers, loc='bottom'). cellText is a list of rows where each row is a list of strings, colLabels are the column headers, and loc places the table — common values are 'bottom', 'center', 'top', and 'right'. The return value is a Table object you can style further.

What is the difference between rowLabels and colLabels?

colLabels are the header cells across the top, one per column. rowLabels are header cells down the left side, one per row. Supplying both gives a labeled grid like a spreadsheet; you can also pass colColours and rowColours to tint those header cells.

How do I style individual table cells?

Get the cell dictionary with the_table.get_celld(), which is keyed by (row, col) where row 0 is the column-header row. Each entry is a Cell you can recolor with set_facecolor(), change text with get_text().set_color(), or scale with the_table.scale(width, height) to make rows taller.

How do I put a table directly under a chart?

Plot your chart on the axes as usual, add the table with loc='bottom', then call plt.subplots_adjust(bottom=0.25) (or similar) to make room. Hiding the x tick labels with ax.set_xticks([]) avoids them clashing with the table's column headers underneath.