Skip to content

Commit aa4bb24

Browse files
feat(plotly): implement andrews-curves (#6820)
## Implementation: `andrews-curves` - python/plotly Implements the **python/plotly** version of `andrews-curves`. **File:** `plots/andrews-curves/implementations/python/plotly.py` **Parent Issue:** #2859 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25918836175)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 1ee898d commit aa4bb24

2 files changed

Lines changed: 215 additions & 184 deletions

File tree

Lines changed: 59 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,113 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
andrews-curves: Andrews Curves for Multivariate Data
3-
Library: plotly 6.5.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-31
3+
Library: plotly 6.7.0 | Python 3.13.13
4+
Quality: 85/100 | Updated: 2026-05-15
55
"""
66

7+
import os
8+
79
import numpy as np
810
import plotly.graph_objects as go
911
from sklearn.datasets import load_iris
1012

1113

12-
# Data - Load Iris dataset
14+
THEME = os.getenv("ANYPLOT_THEME", "light")
15+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
16+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
17+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
18+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
19+
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
20+
21+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"]
22+
BRAND = OKABE_ITO[0]
23+
24+
# Data
1325
iris = load_iris()
14-
X = iris.data # 150 samples, 4 features
26+
X = iris.data
1527
y = iris.target
16-
species_names = ["Setosa", "Versicolor", "Virginica"]
17-
colors = ["#306998", "#FFD43B", "#E74C3C"] # Python Blue, Python Yellow, Red
28+
species_names = iris.target_names
1829

19-
# Normalize data to prevent dominant variables
30+
# Normalize
2031
X_normalized = (X - X.mean(axis=0)) / X.std(axis=0)
2132

22-
# Andrews curve transformation
23-
# f(t) = x1/sqrt(2) + x2*sin(t) + x3*cos(t) + x4*sin(2t) + x5*cos(2t) + ...
33+
# Andrews curve transformation parameter
2434
t = np.linspace(-np.pi, np.pi, 200)
2535

26-
27-
def andrews_curve(x, t_vals):
28-
"""Transform a single observation to Andrews curve values."""
29-
n_features = len(x)
30-
curve = np.ones_like(t_vals) * x[0] / np.sqrt(2)
31-
for i in range(1, n_features):
32-
freq = (i + 1) // 2
33-
if i % 2 == 1:
34-
curve += x[i] * np.sin(freq * t_vals)
35-
else:
36-
curve += x[i] * np.cos(freq * t_vals)
37-
return curve
38-
39-
40-
# Create figure
36+
# Plot
4137
fig = go.Figure()
4238

43-
# Plot Andrews curves for each sample, colored by species
39+
# Plot curves for each sample, colored by species
4440
for species_idx in range(3):
4541
species_mask = y == species_idx
4642
X_species = X_normalized[species_mask]
4743

4844
for i, x in enumerate(X_species):
49-
curve_y = andrews_curve(x, t)
45+
# Inline Andrews curve transformation
46+
n_features = len(x)
47+
curve = np.ones_like(t) * x[0] / np.sqrt(2)
48+
for j in range(1, n_features):
49+
freq = (j + 1) // 2
50+
if j % 2 == 1:
51+
curve += x[j] * np.sin(freq * t)
52+
else:
53+
curve += x[j] * np.cos(freq * t)
54+
5055
fig.add_trace(
5156
go.Scatter(
5257
x=t,
53-
y=curve_y,
58+
y=curve,
5459
mode="lines",
55-
line=dict(color=colors[species_idx], width=2),
60+
line=dict(color=OKABE_ITO[species_idx], width=2),
5661
opacity=0.4,
5762
name=species_names[species_idx],
5863
legendgroup=species_names[species_idx],
59-
showlegend=(i == 0), # Only show legend for first curve of each species
64+
showlegend=(i == 0),
6065
hovertemplate=f"{species_names[species_idx]}<br>t: %{{x:.2f}}<br>f(t): %{{y:.2f}}<extra></extra>",
6166
)
6267
)
6368

64-
# Update layout for 4800x2700 canvas
69+
# Layout
6570
fig.update_layout(
66-
title=dict(text="Iris Dataset · andrews-curves · plotly · pyplots.ai", font=dict(size=32), x=0.5, xanchor="center"),
71+
title=dict(text="andrews-curves · plotly · anyplot.ai", font=dict(size=28, color=INK), x=0.5, xanchor="center"),
6772
xaxis=dict(
68-
title=dict(text="Parameter t (radians)", font=dict(size=24)),
69-
tickfont=dict(size=18),
70-
gridcolor="rgba(128, 128, 128, 0.3)",
71-
gridwidth=1,
73+
title=dict(text="Parameter t (radians)", font=dict(size=22, color=INK)),
74+
tickfont=dict(size=18, color=INK_SOFT),
75+
gridcolor=GRID,
76+
gridwidth=0.5,
7277
zeroline=True,
73-
zerolinecolor="rgba(128, 128, 128, 0.5)",
78+
zerolinecolor=GRID,
7479
zerolinewidth=1,
80+
linecolor=INK_SOFT,
7581
range=[-np.pi, np.pi],
7682
tickvals=[-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi],
7783
ticktext=["-π", "-π/2", "0", "π/2", "π"],
7884
),
7985
yaxis=dict(
80-
title=dict(text="f(t) (normalized units)", font=dict(size=24)),
81-
tickfont=dict(size=18),
82-
gridcolor="rgba(128, 128, 128, 0.3)",
83-
gridwidth=1,
86+
title=dict(text="f(t) (normalized units)", font=dict(size=22, color=INK)),
87+
tickfont=dict(size=18, color=INK_SOFT),
88+
gridcolor=GRID,
89+
gridwidth=0.5,
8490
zeroline=True,
85-
zerolinecolor="rgba(128, 128, 128, 0.5)",
91+
zerolinecolor=GRID,
8692
zerolinewidth=1,
93+
linecolor=INK_SOFT,
8794
),
88-
template="plotly_white",
8995
legend=dict(
90-
font=dict(size=20),
96+
font=dict(size=16, color=INK_SOFT),
9197
x=0.98,
9298
y=0.98,
9399
xanchor="right",
94100
yanchor="top",
95-
bgcolor="rgba(255, 255, 255, 0.8)",
96-
bordercolor="rgba(128, 128, 128, 0.3)",
101+
bgcolor=ELEVATED_BG,
102+
bordercolor=INK_SOFT,
97103
borderwidth=1,
98104
),
99-
margin=dict(l=100, r=80, t=100, b=80),
100-
plot_bgcolor="white",
101-
paper_bgcolor="white",
105+
margin=dict(l=120, r=80, t=100, b=100),
106+
plot_bgcolor=PAGE_BG,
107+
paper_bgcolor=PAGE_BG,
108+
font=dict(color=INK),
102109
)
103110

104-
# Save as PNG (4800 x 2700 px)
105-
fig.write_image("plot.png", width=1600, height=900, scale=3)
106-
107-
# Save interactive HTML version
108-
fig.write_html("plot.html", include_plotlyjs=True, full_html=True)
111+
# Save
112+
fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
113+
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

0 commit comments

Comments
 (0)