Skip to content

Commit 25e5f90

Browse files
feat(bokeh): implement andrews-curves (#6821)
## Implementation: `andrews-curves` - python/bokeh Implements the **python/bokeh** version of `andrews-curves`. **File:** `plots/andrews-curves/implementations/python/bokeh.py` **Parent Issue:** #2859 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25918928390)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 1ddb101 commit 25e5f90

2 files changed

Lines changed: 244 additions & 177 deletions

File tree

Lines changed: 90 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
andrews-curves: Andrews Curves for Multivariate Data
3-
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-30
3+
Library: bokeh 3.9.0 | Python 3.13.13
4+
Quality: 92/100 | Updated: 2026-05-15
55
"""
66

7+
import os
8+
import time
9+
from pathlib import Path
10+
711
import numpy as np
8-
from bokeh.io import export_png, output_file, save
9-
from bokeh.models import ColumnDataSource, Legend
12+
from bokeh.io import output_file, save
13+
from bokeh.models import ColumnDataSource, HoverTool, Legend
1014
from bokeh.plotting import figure
15+
from selenium import webdriver
16+
from selenium.webdriver.chrome.options import Options
1117
from sklearn.datasets import load_iris
1218
from sklearn.preprocessing import StandardScaler
1319

1420

15-
# Load and prepare data
21+
# Theme tokens
22+
THEME = os.getenv("ANYPLOT_THEME", "light")
23+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
24+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
25+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
26+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
27+
28+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"]
29+
30+
# Data
1631
iris = load_iris()
1732
X = iris.data
1833
y = iris.target
@@ -22,77 +37,106 @@
2237
scaler = StandardScaler()
2338
X_scaled = scaler.fit_transform(X)
2439

25-
26-
# Andrews curve function: f(t) = x1/sqrt(2) + x2*sin(t) + x3*cos(t) + x4*sin(2t) + ...
27-
def andrews_curve(coeffs, t):
28-
n = len(coeffs)
29-
result = coeffs[0] / np.sqrt(2)
30-
for i in range(1, n):
31-
if i % 2 == 1:
32-
result += coeffs[i] * np.sin((i // 2 + 1) * t)
33-
else:
34-
result += coeffs[i] * np.cos((i // 2) * t)
35-
return result
36-
37-
3840
# Generate t values from -π to π
3941
t_values = np.linspace(-np.pi, np.pi, 200)
4042

41-
# Colors for each species (Python Blue, Python Yellow, and a colorblind-safe teal)
42-
colors = ["#306998", "#FFD43B", "#2AA198"]
43-
4443
# Create figure
4544
p = figure(
4645
width=4800,
4746
height=2700,
48-
title="andrews-curves · bokeh · pyplots.ai",
47+
title="andrews-curves · bokeh · anyplot.ai",
4948
x_axis_label="t (radians)",
5049
y_axis_label="f(t)",
50+
background_fill_color=PAGE_BG,
51+
border_fill_color=PAGE_BG,
5152
)
5253

54+
# Style text
55+
p.title.text_font_size = "28pt"
56+
p.title.text_color = INK
57+
p.xaxis.axis_label_text_font_size = "22pt"
58+
p.xaxis.axis_label_text_color = INK
59+
p.yaxis.axis_label_text_font_size = "22pt"
60+
p.yaxis.axis_label_text_color = INK
61+
p.xaxis.major_label_text_font_size = "18pt"
62+
p.xaxis.major_label_text_color = INK_SOFT
63+
p.yaxis.major_label_text_font_size = "18pt"
64+
p.yaxis.major_label_text_color = INK_SOFT
65+
66+
# Style axes and grid
67+
p.outline_line_color = INK_SOFT
68+
p.xaxis.axis_line_color = INK_SOFT
69+
p.yaxis.axis_line_color = INK_SOFT
70+
p.xaxis.major_tick_line_color = INK_SOFT
71+
p.yaxis.major_tick_line_color = INK_SOFT
72+
p.xgrid.grid_line_color = INK
73+
p.ygrid.grid_line_color = INK
74+
p.xgrid.grid_line_alpha = 0.10
75+
p.ygrid.grid_line_alpha = 0.10
76+
5377
# Store legend items
5478
legend_items = []
5579

56-
# Plot curves for each species
80+
# Plot curves for each species using vectorized Andrews curves
5781
for species_idx in range(3):
5882
species_mask = y == species_idx
5983
X_species = X_scaled[species_mask]
6084

61-
# Track first line for legend
6285
first_line = None
6386

6487
for coeffs in X_species:
65-
curve_values = andrews_curve(coeffs, t_values)
88+
# Vectorized Andrews curve: f(t) = x1/sqrt(2) + x2*sin(t) + x3*cos(t) + x4*sin(2t) + ...
89+
n = len(coeffs)
90+
curve_values = coeffs[0] / np.sqrt(2)
91+
for i in range(1, n):
92+
if i % 2 == 1:
93+
curve_values += coeffs[i] * np.sin((i // 2 + 1) * t_values)
94+
else:
95+
curve_values += coeffs[i] * np.cos((i // 2) * t_values)
6696

67-
source = ColumnDataSource(data={"x": t_values, "y": curve_values})
97+
source = ColumnDataSource(
98+
data={"x": t_values, "y": curve_values, "species": [species_names[species_idx]] * len(t_values)}
99+
)
68100

69-
line = p.line(x="x", y="y", source=source, line_color=colors[species_idx], line_alpha=0.4, line_width=2)
101+
line = p.line(x="x", y="y", source=source, line_color=OKABE_ITO[species_idx], line_alpha=0.4, line_width=3)
70102

71103
if first_line is None:
72104
first_line = line
73105

74106
legend_items.append((species_names[species_idx], [first_line]))
75107

108+
# Add hover tool
109+
hover = HoverTool(tooltips=[("t", "@x{0.00}"), ("f(t)", "@y{0.00}"), ("Species", "@species")])
110+
p.add_tools(hover)
111+
76112
# Create and add legend
77113
legend = Legend(items=legend_items, location="top_right")
78-
legend.label_text_font_size = "20pt"
79-
legend.background_fill_alpha = 0.8
114+
legend.label_text_font_size = "18pt"
115+
legend.label_text_color = INK_SOFT
116+
legend.background_fill_color = ELEVATED_BG
117+
legend.background_fill_alpha = 0.95
118+
legend.border_line_color = INK_SOFT
80119
p.add_layout(legend, "right")
81120

82-
# Style the plot
83-
p.title.text_font_size = "28pt"
84-
p.xaxis.axis_label_text_font_size = "22pt"
85-
p.yaxis.axis_label_text_font_size = "22pt"
86-
p.xaxis.major_label_text_font_size = "18pt"
87-
p.yaxis.major_label_text_font_size = "18pt"
88-
89-
# Grid styling
90-
p.xgrid.grid_line_alpha = 0.3
91-
p.ygrid.grid_line_alpha = 0.3
92-
p.xgrid.grid_line_dash = "dashed"
93-
p.ygrid.grid_line_dash = "dashed"
94-
95-
# Save as PNG and HTML
96-
export_png(p, filename="plot.png")
97-
output_file("plot.html", title="Andrews Curves - Bokeh")
121+
# Save HTML
122+
output_file(f"plot-{THEME}.html")
98123
save(p)
124+
125+
# Screenshot with headless Chrome via Selenium
126+
W, H = 4800, 2700
127+
opts = Options()
128+
for arg in (
129+
"--headless=new",
130+
"--no-sandbox",
131+
"--disable-dev-shm-usage",
132+
"--disable-gpu",
133+
f"--window-size={W},{H}",
134+
"--hide-scrollbars",
135+
):
136+
opts.add_argument(arg)
137+
driver = webdriver.Chrome(options=opts)
138+
driver.set_window_size(W, H)
139+
driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}")
140+
time.sleep(3)
141+
driver.save_screenshot(f"plot-{THEME}.png")
142+
driver.quit()

0 commit comments

Comments
 (0)