Polar Line Plot — plotnine

A line plot rendered in polar coordinates, where data points are connected with lines around a circular axis. The angle (theta) typically represents a cyclical variable while the radius shows magnitude.

Polar Line Plot rendered with plotnine

Renders

Python source (plotnine)

""" anyplot.ai
polar-line: Polar Line Plot
Library: plotnine 0.15.4 | Python 3.13.13
Quality: 89/100 | Created: 2026-05-12
"""

import os
import sys


# Remove script directory from sys.path to avoid importing local plotnine.py
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if os.path.abspath(p) != script_dir and os.path.abspath(p) != os.getcwd()]

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    element_line,
    element_rect,
    element_text,
    geom_path,
    geom_point,
    ggplot,
    ggsave,
    labs,
    scale_color_manual,
    theme,
    theme_minimal,
    xlim,
    ylim,
)


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"]

# Data: Hourly activity patterns for two weeks
np.random.seed(42)

# Hours as angles (0-360 degrees = 24 hours)
hours = np.arange(0, 24)
hours_deg = hours * 15  # 360 / 24 = 15 degrees per hour

# Week 1: typical activity pattern (higher during day)
week1_activity = 40 + 35 * np.sin(np.radians(hours_deg - 90)) + np.random.randn(24) * 3

# Week 2: shifted pattern (busier mornings)
week2_activity = 45 + 30 * np.sin(np.radians(hours_deg - 120)) + np.random.randn(24) * 3

# Convert polar to Cartesian coordinates
theta_rad_week1 = np.radians(hours_deg)
theta_rad_week2 = np.radians(hours_deg)

# Close the loop
hours_closed = np.append(hours_deg, hours_deg[0])
week1_closed = np.append(week1_activity, week1_activity[0])
week2_closed = np.append(week2_activity, week2_activity[0])
theta_rad_closed = np.append(theta_rad_week1, theta_rad_week1[0])

x_week1 = week1_closed * np.cos(theta_rad_closed)
y_week1 = week1_closed * np.sin(theta_rad_closed)
x_week2 = week2_closed * np.cos(theta_rad_closed)
y_week2 = week2_closed * np.sin(theta_rad_closed)

# Create dataframe
df = pd.DataFrame(
    {
        "x": np.concatenate([x_week1, x_week2]),
        "y": np.concatenate([y_week1, y_week2]),
        "week": ["Week 1"] * len(x_week1) + ["Week 2"] * len(x_week2),
        "order": list(range(len(x_week1))) + list(range(len(x_week2))),
    }
)

# Plot theme
anyplot_theme = theme(
    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
    panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
    panel_grid_major=element_line(color=INK, size=0.2, alpha=0.08),
    panel_grid_minor=element_line(color=INK, size=0.1, alpha=0.04),
    axis_title=element_text(size=20, color=INK),
    axis_text=element_text(size=16, color=INK_SOFT),
    axis_line=element_line(color=INK_SOFT, size=0.5),
    plot_title=element_text(size=24, color=INK),
    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
    legend_text=element_text(size=16, color=INK_SOFT),
    legend_title=element_text(size=16, color=INK),
    figure_size=(16, 9),
)

# Create plot
plot = (
    ggplot(df, aes(x="x", y="y", color="week", order="order"))
    + geom_path(size=1.5, alpha=0.8)
    + geom_point(size=3.5, alpha=0.7)
    + scale_color_manual(values=IMPRINT[:2])
    + xlim(-80, 80)
    + ylim(-80, 80)
    + labs(title="polar-line · plotnine · anyplot.ai", x="", y="", color="Period")
    + theme_minimal()
    + anyplot_theme
)

# Save
output_path = os.path.join(os.path.dirname(__file__), f"plot-{THEME}.png")
ggsave(plot, filename=output_path, dpi=300, width=16, height=9)

Part of Polar Line Plot on anyplot.ai.

Other implementations