Spiral Plot
Chart overview
Spiral plots arrange time-series data along a spiral path, with each revolution representing a cycle (day, week, year).
Key points
- This unique layout effectively reveals periodic patterns and seasonal trends while maintaining the continuous nature of time.
- The form earns its place only when the period is known and fixed in advance: you set one revolution = one cycle, and a signal that repeats on that period lines up radially into a visible spoke, which is why spirals are good for spotting weekly seasonality in hourly data or annual seasonality in daily data.
- Get the period wrong and the pattern smears into a meaningless coil, so a spiral is a confirmation tool, not a discovery tool - use an autocorrelation plot or periodogram first to find the period, then a spiral to show it.
Practical guidance
Perceptually it is demanding: radius conflates with time elapsed, arc length per unit time grows on outer loops, and readers cannot compare magnitudes across revolutions with any precision, so encode the value as color or marker size along the spiral rather than as radial distance, and keep the number of revolutions small. In matplotlib, map time to angle theta = 2*pi*(t mod period)/period and to a slowly growing radius on a polar axis. For most seasonal questions an ordinary line plot with the cycle on the x-axis, or a month-by-year heatmap, communicates faster; reach for the spiral when the continuous, unbroken nature of time is itself part of the story.
Create a Spiral Plot with your data using AI — no coding required.
Python Tutorial
How to create a spiral plot in Python
Use the full tutorial for implementation details, troubleshooting, and chart variations in matplotlib, seaborn, and plotly.
Python Scatter Plot TutorialExample Visualization

Create This Chart Now
Generate publication-ready spiral plots with AI in seconds. No coding required – just describe your data and let AI do the work.
View example prompt
"Create a spiral plot showing 'Daily Step Count' over 2 years (730 days) to reveal seasonal activity patterns. Generate realistic fitness data with patterns: higher activity in spring/summer (8,000-12,000 steps), lower in winter (4,000-7,000 steps), weekly pattern (lower weekends), occasional zero days (sick days). Color points by step count using a YlOrRd gradient. Each spiral revolution = one year. Increase radius linearly from center (Year 1) outward (Year 2). Add concentric reference circles at 5,000 and 10,000 steps. Mark major holidays with annotations. Include a color legend and month labels around the outer edge. Title: 'Two-Year Activity Spiral - Daily Step Count'."
How to create this chart in 30 seconds
Upload Data
Drag & drop your Excel or CSV file. Plotivy securely processes it in your browser.
AI Generation
Our AI analyzes your data and generates the Spiral Plot code automatically.
Customize & Export
Tweak the design with natural language, then export as high-res PNG, SVG or PDF.
Newsletter
Get one weekly tip for better spiral plots
Join researchers receiving concise Python plotting techniques to improve chart clarity and reduce revision cycles.
Python Code Example
# === IMPORTS ===
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.patches import Circle
import matplotlib.dates as mdates
import random
# === USER-EDITABLE PARAMETERS ===
# Data generation parameters
start_date = '2022-01-01' # Change: Start date of the spiral
end_date = '2023-12-31' # Change: End date of the spiral
base_steps_winter = 5000 # Change: Base steps in winter (Dec-Feb)
base_steps_summer = 9000 # Change: Base steps in summer (Jun-Aug)
weekend_reduction = 0.7 # Change: Weekend step reduction factor (0.7 = 30% less)
sick_day_prob = 0.01 # Change: Probability of a zero-step sick day
# Visual parameters
figsize = (10, 10) # Change: Figure size (width, height)
point_size = 30 # Change: Size of scatter points
line_width = 0.5 # Change: Width of connecting lines
month_label_size = 10 # Change: Font size for month labels
holiday_label_size = 9 # Change: Font size for holiday labels
reference_circle_alpha = 0.2 # Change: Opacity of reference circles
reference_circle_color = '#666666' # Change: Color of reference circles
# Color parameters
colormap_name = 'YlOrRd' # Change: Colormap for step counts (YlOrRd, viridis, plasma, etc.)
color_min = 4000 # Change: Minimum step count for color scaling
color_max = 12000 # Change: Maximum step count for color scaling
# More meaningful events to annotate (date, label)
events = [
('2022-04-15', 'Spring Break'),
('2022-09-01', 'Back to School'),
('2022-10-31', 'Halloween'),
('2023-04-15', 'Spring Break'),
('2023-09-01', 'Back to School'),
('2023-10-31', 'Halloween')
]
# === DATA GENERATION ===
date_range = pd.date_range(start=start_date, end=end_date, freq='D')
df = pd.DataFrame({'date': date_range})
df['day_of_year'] = df['date'].dt.dayofyear
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day_of_week'] = df['date'].dt.dayofweek # Monday=0, Sunday=6
df['is_weekend'] = df['day_of_week'] >= 5
# Generate seasonal pattern
df['seasonal_factor'] = np.sin(2 * np.pi * (df['day_of_year'] - 80) / 365) * 0.4 + 1.0
df['base_steps'] = np.where(
df['month'].isin([12, 1, 2]), base_steps_winter,
np.where(df['month'].isin([6, 7, 8]), base_steps_summer,
(base_steps_winter + base_steps_summer) / 2)
)
# Apply seasonal variation and weekly pattern
df['steps'] = df['base_steps'] * df['seasonal_factor']
df.loc[df['is_weekend'], 'steps'] = df.loc[df['is_weekend'], 'steps'] * weekend_reduction
# Add random variation and sick days
df['steps'] = df['steps'] * np.random.normal(1.0, 0.1, len(df))
df.loc[np.random.random(len(df)) < sick_day_prob, 'steps'] = 0
df['steps'] = np.round(df['steps']).astype(int)
df['steps'] = np.clip(df['steps'], 0, 15000) # Cap at reasonable maximum
# === SPIRAL CALCULATION ===
# Convert dates to angles (0-2Ï€ per year)
df['angle'] = 2 * np.pi * (df['day_of_year'] - 1) / 365
# Radius increases linearly with year (0.5 for year 1, 1.0 for year 2)
df['radius'] = 0.5 + 0.5 * (df['year'] - df['year'].min())
# Convert polar to Cartesian coordinates
df['x'] = df['radius'] * np.cos(df['angle'])
df['y'] = df['radius'] * np.sin(df['angle'])
# === PLOT CREATION ===
fig, ax = plt.subplots(figsize=figsize, subplot_kw={'projection': 'polar'})
# Create colormap
cmap = plt.get_cmap(colormap_name)
norm = plt.Normalize(vmin=color_min, vmax=color_max)
# Plot the spiral with color mapping
scatter = ax.scatter(
df['angle'], df['radius'],
c=df['steps'],
cmap=cmap,
norm=norm,
s=point_size,
alpha=0.8,
edgecolors='none'
)
# Connect points with lines to form spiral
for year in df['year'].unique():
year_df = df[df['year'] == year].sort_values('day_of_year')
ax.plot(year_df['angle'], year_df['radius'],
color='#555555',
linewidth=line_width,
alpha=0.3)
# Add year circles
print("The solid circles represent each year in the visualization:")
for year in df['year'].unique():
radius = 0.5 + 0.5 * (year - df['year'].min())
circle = Circle((0, 0), radius,
fill=False,
linestyle='-',
linewidth=1.5,
alpha=reference_circle_alpha,
color=reference_circle_color)
ax.add_patch(circle)
ax.text(0, radius, f'{year}',
ha='center', va='center',
fontsize=10,
color=reference_circle_color,
bbox=dict(boxstyle='round', facecolor='white', alpha=0.7))
# Add month labels around the outer edge
month_angles = {1: 0, 2: np.pi/6, 3: np.pi/3, 4: np.pi/2,
5: 2*np.pi/3, 6: 5*np.pi/6, 7: np.pi,
8: 7*np.pi/6, 9: 4*np.pi/3, 10: 3*np.pi/2,
11: 5*np.pi/3, 12: 11*np.pi/6}
month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
for month, angle in month_angles.items():
ax.text(angle, 1.15, month_names[month-1],
ha='center', va='center',
fontsize=month_label_size,
bbox=dict(boxstyle='round', facecolor='white', alpha=0.7))
# Add event annotations (spaced to avoid overlap)
for date, label in events:
event_row = df[df['date'] == date].iloc[0]
ax.annotate(label,
xy=(event_row['angle'], event_row['radius']),
xytext=(10, 10),
textcoords='offset points',
fontsize=holiday_label_size,
arrowprops=dict(arrowstyle='->', color='#333333'),
bbox=dict(boxstyle='round', pad=0.2, facecolor='white', alpha=0.7))
# Customize plot appearance
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_xticks([])
ax.set_yticks([])
ax.set_ylim(0, 1.2)
ax.spines['polar'].set_visible(False)
# Add horizontal colorbar below the graph with reduced padding
cbar = plt.colorbar(scatter, ax=ax, orientation='horizontal', pad=0.05)
cbar.set_label('Daily Step Count', labelpad=10)
# Add title
plt.title('Two-Year Activity Spiral - Daily Step Count',
pad=20, fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# END-OF-CODEOpens the Analyze page with this code pre-loaded and ready to execute
Console Output
730 days plotted across 2.0 spiral turns Mean daily steps: 7987
Common Use Cases
- 1Seasonal pattern detection
- 2Cyclical trend analysis
- 3Activity tracking over time
- 4Weather pattern visualization
Pro Tips
Choose spiral period to match data cycle
Use color to encode values
Add radial reference lines
Frequently asked questions
When should you use a spiral plot?
Spiral plots arrange time-series data along a spiral path, with each revolution representing a cycle (day, week, year). This unique layout effectively reveals periodic patterns and seasonal trends while maintaining the continuous nature of time. Common applications include seasonal pattern detection, cyclical trend analysis, and activity tracking over time.
Which Python libraries can create a spiral plot?
A spiral plot can be built in Python with matplotlib — matplotlib for precise control over axes, annotations, and journal styling. In Plotivy you describe the figure and it writes the matplotlib code for you.
Can I make a spiral plot without writing Python code?
Yes. Describe the spiral plot you need in plain language and upload your dataset — Plotivy's AI writes the Python code and renders a publication-ready figure. You still get the full, editable matplotlib source, so nothing is locked in a black box.
What are best practices for a clear spiral plot?
Choose spiral period to match data cycle. Use color to encode values.
Long-tail keyword opportunities
High-intent chart variations
Library comparison for this chart
matplotlib
Best when you need full control over axis formatting, annotation placement, and journal-specific styling for spiral-plot.
Scientific Chart Selection Cheat Sheet
Not sure whether to use a Violin Plot, Box Plot, or Ridge Plot? Download our single-page reference mapping the most-used scientific chart types, exactly when to use them, and the core Matplotlib/Seaborn functions.