Skip to content

Commit 6f2a4ad

Browse files
feat(highcharts): implement andrews-curves (#6826)
## Implementation: `andrews-curves` - python/highcharts Implements the **python/highcharts** version of `andrews-curves`. **File:** `plots/andrews-curves/implementations/python/highcharts.py` **Parent Issue:** #2859 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25919432228)* --------- 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 94888bd commit 6f2a4ad

2 files changed

Lines changed: 264 additions & 88 deletions

File tree

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
andrews-curves: Andrews Curves for Multivariate Data
3-
Library: highcharts unknown | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-31
3+
Library: highcharts unknown | Python 3.13.13
4+
Quality: 83/100 | Updated: 2026-05-15
55
"""
66

7+
import os
78
import tempfile
89
import time
910
import urllib.request
@@ -12,25 +13,32 @@
1213
import numpy as np
1314
from highcharts_core.chart import Chart
1415
from highcharts_core.options import HighchartsOptions
15-
from highcharts_core.options.series.area import LineSeries
16+
from highcharts_core.options.series.spline import SplineSeries
1617
from selenium import webdriver
1718
from selenium.webdriver.chrome.options import Options
1819
from sklearn.datasets import load_iris
1920
from sklearn.preprocessing import StandardScaler
2021

2122

22-
# Data - Iris dataset normalized
23+
THEME = os.getenv("ANYPLOT_THEME", "light")
24+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
25+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
26+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
27+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
28+
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
29+
30+
BRAND = "#009E73"
31+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"]
32+
2333
iris = load_iris()
2434
X = StandardScaler().fit_transform(iris.data)
2535
y = iris.target
2636
species_names = ["Setosa", "Versicolor", "Virginica"]
2737

28-
# Andrews curve transformation: f(t) = x1/sqrt(2) + x2*sin(t) + x3*cos(t) + x4*sin(2t) + ...
2938
t = np.linspace(-np.pi, np.pi, 200)
3039

3140

3241
def andrews_curve(x, t):
33-
"""Transform multivariate observation to Andrews curve"""
3442
n = len(x)
3543
result = x[0] / np.sqrt(2)
3644
for i in range(1, n):
@@ -41,77 +49,67 @@ def andrews_curve(x, t):
4149
return result
4250

4351

44-
# Create chart
4552
chart = Chart(container="container")
4653
chart.options = HighchartsOptions()
4754

48-
# Chart settings
4955
chart.options.chart = {
5056
"type": "line",
5157
"width": 4800,
5258
"height": 2700,
53-
"backgroundColor": "#ffffff",
59+
"backgroundColor": PAGE_BG,
5460
"marginBottom": 180,
5561
"marginTop": 120,
5662
"marginLeft": 150,
5763
"marginRight": 100,
5864
}
5965

60-
# Title
6166
chart.options.title = {
62-
"text": "Iris Species · andrews-curves · highcharts · pyplots.ai",
63-
"style": {"fontSize": "56px", "fontWeight": "bold"},
64-
"y": 60,
67+
"text": "andrews-curves · highcharts · anyplot.ai",
68+
"style": {"fontSize": "28px", "color": INK},
69+
"y": 30,
6570
}
6671

67-
# Axes
6872
chart.options.x_axis = {
69-
"title": {"text": "t (radians)", "style": {"fontSize": "40px"}, "margin": 30},
70-
"labels": {"style": {"fontSize": "32px"}, "y": 40},
71-
"gridLineWidth": 1,
72-
"gridLineColor": "rgba(0,0,0,0.15)",
73+
"title": {"text": "t (radians)", "style": {"fontSize": "22px", "color": INK}},
74+
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
75+
"lineColor": INK_SOFT,
76+
"tickColor": INK_SOFT,
77+
"gridLineColor": GRID,
7378
"tickInterval": 1,
7479
"min": -3.15,
7580
"max": 3.15,
7681
}
7782

7883
chart.options.y_axis = {
79-
"title": {"text": "f(t)", "style": {"fontSize": "40px"}, "margin": 30},
80-
"labels": {"style": {"fontSize": "32px"}, "x": -15},
81-
"gridLineWidth": 1,
82-
"gridLineColor": "rgba(0,0,0,0.15)",
84+
"title": {"text": "f(t)", "style": {"fontSize": "22px", "color": INK}},
85+
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
86+
"lineColor": INK_SOFT,
87+
"tickColor": INK_SOFT,
88+
"gridLineColor": GRID,
8389
"tickInterval": 1,
8490
}
8591

86-
# Colors for species (colorblind-safe)
87-
colors = ["#306998", "#FFD43B", "#9467BD"]
88-
89-
# Legend styling - positioned inside chart area to avoid clipping
9092
chart.options.legend = {
9193
"enabled": True,
92-
"itemStyle": {"fontSize": "36px", "fontWeight": "bold"},
93-
"symbolWidth": 60,
94-
"symbolHeight": 4,
94+
"itemStyle": {"fontSize": "18px", "color": INK_SOFT},
95+
"symbolWidth": 30,
96+
"symbolHeight": 2,
9597
"align": "right",
9698
"verticalAlign": "top",
9799
"layout": "vertical",
98100
"x": -50,
99-
"y": 80,
100-
"itemDistance": 20,
101-
"itemMarginTop": 10,
102-
"backgroundColor": "rgba(255,255,255,0.9)",
101+
"y": 50,
102+
"backgroundColor": ELEVATED_BG,
103103
"borderWidth": 1,
104-
"borderColor": "#cccccc",
104+
"borderColor": INK_SOFT,
105105
"padding": 15,
106106
}
107107

108-
# Plot options for transparency
109108
chart.options.plot_options = {
110109
"line": {"lineWidth": 3, "marker": {"enabled": False}, "animation": False},
111110
"series": {"animation": False},
112111
}
113112

114-
# Generate curves for each observation (sample 20 per species for clarity)
115113
np.random.seed(42)
116114
samples_per_species = 20
117115

@@ -124,36 +122,36 @@ def andrews_curve(x, t):
124122
curve_values = andrews_curve(species_X[idx], t)
125123
data_points = [[float(t[j]), float(curve_values[j])] for j in range(len(t))]
126124

127-
series = LineSeries()
125+
series = SplineSeries()
128126
series.data = data_points
129127
series.name = species_names[species_idx]
130-
series.color = colors[species_idx]
128+
series.color = OKABE_ITO[species_idx]
131129
series.opacity = 0.5
132-
series.show_in_legend = i == 0 # Only show first curve in legend
130+
series.show_in_legend = i == 0
133131
series.line_width = 2
134132

135133
chart.add_series(series)
136134

137-
# Download Highcharts JS
138-
highcharts_url = "https://code.highcharts.com/highcharts.js"
135+
highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts@latest/highcharts.js"
139136
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
140137
highcharts_js = response.read().decode("utf-8")
141138

142-
# Generate HTML with inline script
143139
html_str = chart.to_js_literal()
144140
html_content = f"""<!DOCTYPE html>
145141
<html>
146142
<head>
147143
<meta charset="utf-8">
148144
<script>{highcharts_js}</script>
149145
</head>
150-
<body style="margin:0;">
146+
<body style="margin:0; background:{PAGE_BG};">
151147
<div id="container" style="width: 4800px; height: 2700px;"></div>
152148
<script>{html_str}</script>
153149
</body>
154150
</html>"""
155151

156-
# Write temp HTML and capture screenshot
152+
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
153+
f.write(html_content)
154+
157155
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
158156
f.write(html_content)
159157
temp_path = f.name
@@ -163,19 +161,12 @@ def andrews_curve(x, t):
163161
chrome_options.add_argument("--no-sandbox")
164162
chrome_options.add_argument("--disable-dev-shm-usage")
165163
chrome_options.add_argument("--disable-gpu")
166-
chrome_options.add_argument("--window-size=4900,2800")
164+
chrome_options.add_argument("--window-size=4800,2700")
167165

168166
driver = webdriver.Chrome(options=chrome_options)
169167
driver.get(f"file://{temp_path}")
170168
time.sleep(5)
171-
172-
# Screenshot the chart element for exact dimensions
173-
container = driver.find_element("id", "container")
174-
container.screenshot("plot.png")
169+
driver.save_screenshot(f"plot-{THEME}.png")
175170
driver.quit()
176171

177172
Path(temp_path).unlink()
178-
179-
# Also save HTML for interactive version
180-
with open("plot.html", "w", encoding="utf-8") as f:
181-
f.write(html_content)

0 commit comments

Comments
 (0)