Skip to content

Polar Plots

Polar plots display data in circular coordinates (angle and radius), useful for directional data, periodic phenomena, and radar charts.

Creating Polar Axes

Method 1: subplot with projection

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

theta = np.linspace(0, 2 * np.pi, 100)
r = 1 + np.sin(theta)

ax.plot(theta, r)
plt.show()

Method 2: add_subplot with polar=True

fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.plot(theta, r)
plt.show()

Method 3: plt.polar (pyplot interface)

plt.polar(theta, r)
plt.show()

Basic Polar Line Plot

import matplotlib.pyplot as plt
import numpy as np

theta = np.linspace(0, 2 * np.pi, 100)
r = np.abs(np.sin(2 * theta) * np.cos(2 * theta))

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r, 'b-', linewidth=2)
ax.set_title('Rose Curve')
plt.show()

Common Polar Curves

Cardioid

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
theta = np.linspace(0, 2 * np.pi, 100)
r = 1 + np.cos(theta)  # Cardioid: r = 1 + cos(θ)
ax.plot(theta, r)
ax.set_title('Cardioid')
plt.show()

Rose Curves

fig, axes = plt.subplots(1, 3, subplot_kw={'projection': 'polar'}, figsize=(12, 4))

theta = np.linspace(0, 2 * np.pi, 1000)

# n=2: 4 petals
axes[0].plot(theta, np.sin(2 * theta))
axes[0].set_title('r = sin(2θ)')

# n=3: 3 petals
axes[1].plot(theta, np.sin(3 * theta))
axes[1].set_title('r = sin(3θ)')

# n=5: 5 petals
axes[2].plot(theta, np.sin(5 * theta))
axes[2].set_title('r = sin(5θ)')

plt.tight_layout()
plt.show()

Spiral

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
theta = np.linspace(0, 6 * np.pi, 500)
r = theta  # Archimedean spiral

ax.plot(theta, r)
ax.set_title('Archimedean Spiral')
plt.show()

Polar Scatter Plot

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(42)
n = 100
theta = 2 * np.pi * np.random.rand(n)
r = np.random.rand(n)
colors = np.random.rand(n)
sizes = 100 * np.random.rand(n)

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
scatter = ax.scatter(theta, r, c=colors, s=sizes, cmap='viridis', alpha=0.7)
plt.colorbar(scatter)
plt.show()

Polar Bar Chart (Radar/Spider Chart)

Basic Radar Chart

import matplotlib.pyplot as plt
import numpy as np

categories = ['Speed', 'Reliability', 'Comfort', 'Safety', 'Efficiency']
n_cats = len(categories)

# Values for two products
values1 = [4, 3, 5, 4, 3]
values2 = [3, 5, 3, 5, 4]

# Compute angle for each category
angles = np.linspace(0, 2 * np.pi, n_cats, endpoint=False).tolist()

# Close the plot
values1 += values1[:1]
values2 += values2[:1]
angles += angles[:1]

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

ax.plot(angles, values1, 'o-', linewidth=2, label='Product A')
ax.fill(angles, values1, alpha=0.25)

ax.plot(angles, values2, 'o-', linewidth=2, label='Product B')
ax.fill(angles, values2, alpha=0.25)

ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
ax.set_ylim(0, 5)
ax.legend(loc='upper right')
ax.set_title('Product Comparison')
plt.show()

Radar Chart Function

def radar_chart(categories, values_dict, title='Radar Chart'):
    """
    Create radar chart for multiple series.

    Parameters:
    - categories: list of category names
    - values_dict: dict mapping series names to value lists
    - title: chart title
    """
    n_cats = len(categories)
    angles = np.linspace(0, 2 * np.pi, n_cats, endpoint=False).tolist()
    angles += angles[:1]  # Close the plot

    fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(8, 8))

    for name, values in values_dict.items():
        vals = values + values[:1]  # Close the plot
        ax.plot(angles, vals, 'o-', linewidth=2, label=name)
        ax.fill(angles, vals, alpha=0.2)

    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(categories)
    ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
    ax.set_title(title)

    return fig, ax

# Usage
categories = ['A', 'B', 'C', 'D', 'E']
values_dict = {
    'Series 1': [4, 3, 5, 2, 4],
    'Series 2': [3, 4, 3, 4, 5],
    'Series 3': [5, 2, 4, 3, 3]
}
radar_chart(categories, values_dict)
plt.show()

Polar Fill

Fill Between

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

theta = np.linspace(0, 2 * np.pi, 100)
r1 = 1 + 0.5 * np.sin(3 * theta)
r2 = 2 + 0.5 * np.cos(3 * theta)

ax.fill_between(theta, r1, r2, alpha=0.3)
ax.plot(theta, r1, 'b-')
ax.plot(theta, r2, 'r-')
plt.show()

Filled Area

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
theta = np.linspace(0, 2 * np.pi, 100)
r = 1 + np.sin(theta)

ax.fill(theta, r, alpha=0.5, color='blue')
ax.plot(theta, r, 'b-', linewidth=2)
plt.show()

Customizing Polar Axes

Angular Ticks (Theta)

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r)

# Set theta tick locations (in radians)
ax.set_xticks(np.linspace(0, 2*np.pi, 8, endpoint=False))

# Set theta tick labels
ax.set_xticklabels(['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'])

plt.show()

Radial Ticks

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r)

# Set radial limits
ax.set_rlim(0, 2)

# Set radial ticks
ax.set_rticks([0.5, 1, 1.5, 2])

plt.show()

Theta Direction and Zero Position

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r)

# Set zero position (default is 'E' = right)
ax.set_theta_zero_location('N')  # 0 degrees at top

# Set direction (default is counter-clockwise)
ax.set_theta_direction(-1)  # Clockwise

plt.show()

Grid Styling

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r)

ax.grid(True, linestyle='--', alpha=0.7)
ax.set_facecolor('lightyellow')

plt.show()

Practical Examples

1. Wind Rose Diagram

import matplotlib.pyplot as plt
import numpy as np

# Wind direction data (degrees)
directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
n_dirs = len(directions)
angles = np.linspace(0, 2*np.pi, n_dirs, endpoint=False)

# Wind frequency by direction and speed category
speeds = {
    '0-5 m/s': [10, 5, 8, 12, 15, 10, 7, 8],
    '5-10 m/s': [5, 8, 12, 8, 10, 7, 5, 6],
    '10+ m/s': [2, 3, 5, 3, 4, 2, 1, 2]
}

width = 2*np.pi / n_dirs * 0.8  # Bar width

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10, 10))

bottom = np.zeros(n_dirs)
for speed, values in speeds.items():
    bars = ax.bar(angles, values, width=width, bottom=bottom, label=speed, alpha=0.7)
    bottom += values

ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_xticks(angles)
ax.set_xticklabels(directions)
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
ax.set_title('Wind Rose Diagram')
plt.show()

2. Daily Activity Pattern

import matplotlib.pyplot as plt
import numpy as np

hours = np.arange(0, 24)
angles = hours * 2 * np.pi / 24

# Activity levels throughout the day
activity = [2, 1, 1, 1, 1, 2, 4, 6, 8, 9, 8, 7,
            6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 3, 2]

# Close the loop
activity = activity + [activity[0]]
angles = np.append(angles, angles[0])

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

ax.plot(angles, activity, 'b-', linewidth=2)
ax.fill(angles, activity, alpha=0.3)

ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_xticks(np.linspace(0, 2*np.pi, 24, endpoint=False))
ax.set_xticklabels([f'{h:02d}:00' for h in range(24)])
ax.set_title('Daily Activity Pattern')
plt.show()

3. Antenna Radiation Pattern

import matplotlib.pyplot as plt
import numpy as np

theta = np.linspace(0, 2*np.pi, 360)

# Dipole antenna pattern (simplified)
r_dipole = np.abs(np.sin(theta))

# Directional antenna pattern
r_directional = np.abs(np.cos(theta))**2

fig, (ax1, ax2) = plt.subplots(1, 2, subplot_kw={'projection': 'polar'}, figsize=(12, 5))

ax1.plot(theta, r_dipole)
ax1.fill(theta, r_dipole, alpha=0.3)
ax1.set_title('Dipole Pattern')

ax2.plot(theta, r_directional)
ax2.fill(theta, r_directional, alpha=0.3)
ax2.set_title('Directional Pattern')

plt.tight_layout()
plt.show()

4. Sector Performance

import matplotlib.pyplot as plt
import numpy as np

sectors = ['Tech', 'Finance', 'Healthcare', 'Energy', 
           'Consumer', 'Industrial', 'Materials', 'Utilities']
n_sectors = len(sectors)
angles = np.linspace(0, 2*np.pi, n_sectors, endpoint=False)

# Performance metrics
returns = [15, 8, 12, -5, 7, 10, 3, 5]  # %

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10, 10))

# Normalize for visualization (shift to positive)
r = np.array(returns) + 10

bars = ax.bar(angles, r, width=0.6, alpha=0.7,
              color=['green' if ret > 0 else 'red' for ret in returns])

ax.set_xticks(angles)
ax.set_xticklabels(sectors)
ax.set_ylim(0, 30)
ax.set_rticks([5, 10, 15, 20, 25])
ax.set_yticklabels(['-5%', '0%', '5%', '10%', '15%'])
ax.set_title('Sector Performance')
plt.show()

Multiple Polar Subplots

fig, axes = plt.subplots(2, 2, subplot_kw={'projection': 'polar'}, figsize=(10, 10))

theta = np.linspace(0, 2*np.pi, 100)

curves = [
    ('Cardioid', 1 + np.cos(theta)),
    ('Rose (n=3)', np.sin(3*theta)),
    ('Spiral', theta/10),
    ('Lemniscate', np.sqrt(np.abs(np.cos(2*theta))))
]

for ax, (name, r) in zip(axes.flat, curves):
    ax.plot(theta, r)
    ax.set_title(name)

plt.tight_layout()
plt.show()

Key Methods for Polar Axes

Method Description
set_theta_zero_location(loc) Position of θ=0 ('N', 'E', 'S', 'W')
set_theta_direction(direction) 1=counterclockwise, -1=clockwise
set_rlim(min, max) Set radial limits
set_rticks(ticks) Set radial tick positions
set_xticks(ticks) Set angular tick positions
set_xticklabels(labels) Set angular tick labels
set_rgrids(radii) Set radial grid lines
set_thetagrids(angles) Set angular grid lines