When I first started building custom data visualizations in Python, I often found myself needing to highlight specific regions on a chart. Whether it’s marking recession periods in economic data or outlining specific clusters in a scatter plot, knowing how to draw shapes is a fundamental skill.
In this tutorial, I will show you exactly how I plot multiple rectangles in Matplotlib using a few different professional approaches.
The Common Challenge with Multiple Shapes
Adding a single rectangle in Matplotlib is easy using patches.Rectangle. However, when you have a dataset with dozens of coordinates, adding them one by one becomes inefficient.
Over the years, I’ve found that the best method depends on whether your rectangles share the same styling or if they need to be treated as individual entities.
Below, I’ll walk you through the methods I use in my daily development work, utilizing examples relevant to US-based data analysis.
Method 1: Use a Loop with patches.Rectangle
This is the most intuitive method. I use this when I have a small number of rectangles (usually under 20), and I want to maintain individual control over each one.
In this example, let’s visualize different “Zoning Districts” in a hypothetical US city block layout.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def plot_zoning_districts():
# Create a figure and axis
fig, ax = plt.subplots(figsize=(10, 8))
# Real-world US Context: Mapping out property lots in a development
# Format: [x, y, width, height, label, color]
lots = [
[1, 1, 2, 3, 'Residential A', '#ff9999'],
[4, 1, 2, 3, 'Residential B', '#66b3ff'],
[7, 1, 3, 5, 'Commercial Zone', '#99ff99'],
[1, 5, 5, 2, 'Public Park', '#ffcc99'],
]
for lot in lots:
x, y, w, h, label, color = lot
# Creating the rectangle object
rect = patches.Rectangle((x, y), w, h, linewidth=2,
edgecolor='black', facecolor=color, alpha=0.7)
# Adding the patch to the axes
ax.add_patch(rect)
# Adding a text label in the center of the rectangle
ax.text(x + w/2, y + h/2, label, ha='center', va='center', weight='bold')
# Setting plot limits based on our "city block"
ax.set_xlim(0, 12)
ax.set_ylim(0, 10)
ax.set_aspect('equal')
plt.title('Urban Planning Map: Downtown District Layout', fontsize=14)
plt.xlabel('West-East Street Grid (Blocks)')
plt.ylabel('North-South Street Grid (Blocks)')
plt.grid(linestyle='--', alpha=0.5)
plt.show()
plot_zoning_districts()You can refer to the screenshot below to see the output.

I prefer this when labels are necessary. By looping, I can easily calculate the center point of each rectangle to place descriptive text, which is a common requirement in architectural or logistical plotting.
Method 2: Use PatchCollection for High Performance
When I’m dealing with hundreds of rectangles, such as visualizing thousands of sensor placements or a dense heat map, looping becomes slow.
The PatchCollection class is built for speed. It allows Matplotlib to render all shapes in a single draw call. Here, we will simulate analyzing the US Treasury Yield curve segments or “Risk Buckets.”
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.collections import PatchCollection
import numpy as np
def plot_financial_risk_buckets():
fig, ax = plt.subplots(figsize=(12, 6))
# Generating a larger number of rectangles (e.g., US Market Data Segments)
rects = []
# Let's create a grid representing different market sectors
for i in range(10):
for j in range(5):
# x, y coordinates and width/height
# Representing "Volatility" vs "Return" segments
rect = patches.Rectangle((i*1.1, j*1.1), 1, 1)
rects.append(rect)
# Create the collection
# We can pass a colormap to color the rectangles based on data
pc = PatchCollection(rects, cmap='RdYlGn', edgecolors='black', alpha=0.8)
# Simulating "Performance Data" for each rectangle (0 to 100)
performance_data = np.random.randint(0, 100, len(rects))
pc.set_array(performance_data)
# Add the collection to the plot
ax.add_collection(pc)
# Styling the plot
ax.set_xlim(-1, 12)
ax.set_ylim(-1, 7)
# Adding a colorbar to show performance
cbar = fig.colorbar(pc, ax=ax)
cbar.set_label('Portfolio Performance Index (%)')
plt.title('US Equities Market Sector Analysis (Heatmap Grid)', fontsize=15)
plt.xlabel('Sector Risk Profile')
plt.ylabel('Market Capitalization Tier')
plt.tight_layout()
plt.show()
plot_financial_risk_buckets()You can refer to the screenshot below to see the output.

Notice I didn’t set colors in the loop. By using pc.set_array(), I can map a list of values to a colormap. This is the professional way to handle large-scale data visualization in Matplotlib.
Method 3: Highlight Time-Series Data (Recession Shading)
In the US financial industry, a very common task is shading multiple “recession periods” on a stock price chart.
While you could use axvspan, using Rectangle gives you more control if the “shading” needs to have specific heights or borders.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import pandas as pd
def plot_us_economic_recessions():
# Simulated S&P 500 Style Growth Data
years = list(range(1990, 2025))
values = [100 + (i*5) + (10 if i > 15 else 0) for i in range(len(years))]
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(years, values, color='navy', linewidth=2, label='Economic Index')
# Define US Recession periods (Approximate dates)
# (Start Year, Duration in Years)
recessions = [
(1990, 1),
(2001, 0.8),
(2008, 1.5),
(2020, 0.5)
]
for start, duration in recessions:
# Drawing a rectangle that covers the full height of the plot
# Rectangle((x, y), width, height)
rect = patches.Rectangle((start, 0), duration, 300,
linewidth=0, facecolor='red', alpha=0.2)
ax.add_patch(rect)
ax.set_ylim(80, 280)
plt.title('Historical Performance of US Markets with Recession Overlays', fontsize=14)
plt.xlabel('Year')
plt.ylabel('Index Value (USD)')
plt.legend()
plt.show()
plot_us_economic_recessions()You can refer to the screenshot below to see the output.

Key Customization Options
When you are plotting these shapes, there are four parameters I always tweak to make the plot look professional:
- alpha: Never leave it at 1.0 if shapes overlap. Setting it to 0.5 or 0.7 makes the grid lines visible behind the shapes.
- joinstyle: If you have thick borders, use joinstyle=’round’ or ‘miter’ to control how corners look.
- zorder: If your rectangles are hiding your data points, set zorder=1 for rectangles and zorder=2 for your lines/scatters.
- hatch: In US government reports, we often use patterns (like ‘///’ or ‘…’) for accessibility in black-and-white printing. You can add hatch=’///’ to any Rectangle object.
You may also read:
- Set the Secondary Axis Range in Matplotlib
- How to Set Axis Lower Limit in Matplotlib
- Create Subplots with a Secondary Y-Axis in Matplotlib
- How to Plot a Matplotlib Secondary Y-Axis with a Log Scale

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.