Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 58 additions & 50 deletions plots/rose-basic/implementations/python/bokeh.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,60 @@
""" pyplots.ai
""" anyplot.ai
rose-basic: Basic Rose Chart
Library: bokeh 3.8.1 | Python 3.13.11
Quality: 92/100 | Created: 2025-12-23
Library: bokeh 3.9.0 | Python 3.13.13
Quality: 84/100 | Updated: 2026-04-30
"""

import os
import sys


# Remove this script's directory from sys.path to prevent bokeh.py from
# shadowing the installed bokeh package when Python adds the script dir.
_script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if os.path.abspath(p or ".") != _script_dir]

import numpy as np
from bokeh.io import export_png, output_file, save
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
BRAND = "#009E73"

# Data - Monthly rainfall (mm) showing seasonal patterns
np.random.seed(42)
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
values = [85, 70, 65, 45, 30, 20, 15, 25, 40, 60, 75, 90]

max_val = max(values)
n = len(months)
angle_width = 2 * np.pi / n

# Calculate wedge angles (equal slices, starting from top/north)
angle_width = 2 * np.pi / n
start_angles = np.array([np.pi / 2 - angle_width / 2 - i * angle_width for i in range(n)])
end_angles = start_angles - angle_width
center_angles = (start_angles + end_angles) / 2

# Normalize values to radius (max value = 1.0 for full radius)
max_val = max(values)
# Normalize values to radius (max value = 1.0)
radii = [v / max_val for v in values]

# Colors - gradient from Python Yellow to Python Blue based on value
colors = ["#306998" if v >= 60 else "#4A89B8" if v >= 40 else "#7AB3D8" if v >= 25 else "#FFD43B" for v in values]
# Brand green with alpha varying by rainfall intensity
alphas = [0.35 + 0.65 * (v / max_val) for v in values]

source = ColumnDataSource(
data={
"start_angle": start_angles,
"end_angle": end_angles,
"radius": radii,
"months": months,
"values": values,
"colors": colors,
}
data={"start_angle": start_angles, "end_angle": end_angles, "radius": radii, "alphas": alphas}
)

# Create figure with matching x/y ranges for circular shape
# Create figure
p = figure(
width=4800,
height=2700,
title="Monthly Rainfall · rose-basic · bokeh · pyplots.ai",
x_range=(-1.3, 1.3),
y_range=(-1.2, 1.1),
title="Monthly Rainfall · rose-basic · bokeh · anyplot.ai",
x_range=(-1.5, 1.5),
y_range=(-1.3, 1.35),
tools="",
toolbar_location=None,
)
Expand All @@ -59,56 +67,56 @@
start_angle="end_angle",
end_angle="start_angle",
source=source,
fill_color="colors",
fill_alpha=0.8,
line_color="white",
fill_color=BRAND,
fill_alpha="alphas",
line_color=PAGE_BG,
line_width=2,
)

# Add radial gridlines (concentric circles)
# Radial gridlines (concentric circles)
theta = np.linspace(0, 2 * np.pi, 200)
for r in [0.25, 0.5, 0.75, 1.0]:
theta = np.linspace(0, 2 * np.pi, 100)
p.line(r * np.cos(theta), r * np.sin(theta), line_color="gray", line_alpha=0.3, line_width=1, line_dash="dashed")
p.line(r * np.cos(theta), r * np.sin(theta), line_color=INK, line_alpha=0.22, line_width=1.5, line_dash="dashed")

# Add radial lines from center
# Radial divider lines from center
for i in range(n):
angle = np.pi / 2 - i * angle_width
p.line([0, 1.05 * np.cos(angle)], [0, 1.05 * np.sin(angle)], line_color="gray", line_alpha=0.2, line_width=1)
p.line([0, 1.05 * np.cos(angle)], [0, 1.05 * np.sin(angle)], line_color=INK, line_alpha=0.18, line_width=1)

# Add month labels around the outside
label_radius = 1.12
# Month labels centered in each wedge
label_radius = 1.15
for i, month in enumerate(months):
angle = np.pi / 2 - i * angle_width
x = label_radius * np.cos(angle)
y = label_radius * np.sin(angle)
angle = center_angles[i]
p.text(
x=[x],
y=[y],
x=[label_radius * np.cos(angle)],
y=[label_radius * np.sin(angle)],
text=[month],
text_align="center",
text_baseline="middle",
text_font_size="18pt",
text_color="#333333",
text_font_size="20pt",
text_color=INK,
)

# Add value scale labels on right side
p.text(x=[1.05], y=[0.25], text=["25%"], text_font_size="14pt", text_color="gray", text_align="left")
p.text(x=[1.05], y=[0.5], text=["50%"], text_font_size="14pt", text_color="gray", text_align="left")
p.text(x=[1.05], y=[0.75], text=["75%"], text_font_size="14pt", text_color="gray", text_align="left")
p.text(x=[1.05], y=[1.0], text=["100%"], text_font_size="14pt", text_color="gray", text_align="left")
# Rainfall scale labels (actual mm values, right side)
for r in [0.25, 0.5, 0.75, 1.0]:
val_label = f"{int(r * max_val + 0.5)} mm"
p.text(x=[1.2], y=[r], text=[val_label], text_font_size="15pt", text_color=INK_SOFT, text_align="left")

# Styling
# Title and chrome
p.title.text_font_size = "28pt"
p.title.align = "center"
p.background_fill_color = "white"
p.border_fill_color = "white"
p.title.text_color = INK
p.title.text_font_style = "normal"

p.background_fill_color = PAGE_BG
p.border_fill_color = PAGE_BG
p.outline_line_color = None

# Hide axes (not needed for rose chart)
p.axis.visible = False
p.grid.visible = False

# Save as PNG and HTML
export_png(p, filename="plot.png")
output_file("plot.html")
# Save
export_png(p, filename=f"plot-{THEME}.png")
output_file(f"plot-{THEME}.html")
save(p)
Loading
Loading