Skip to content

Commit a5fcc2d

Browse files
feat(plotly): implement chernoff-basic (#6830)
## Implementation: `chernoff-basic` - python/plotly Implements the **python/plotly** version of `chernoff-basic`. **File:** `plots/chernoff-basic/implementations/python/plotly.py` **Parent Issue:** #3003 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25925129018)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 32c93d1 commit a5fcc2d

2 files changed

Lines changed: 211 additions & 176 deletions

File tree

plots/chernoff-basic/implementations/python/plotly.py

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
chernoff-basic: Chernoff Faces 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: 92/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

14+
# Theme tokens
15+
THEME = os.getenv("ANYPLOT_THEME", "light")
16+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
17+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
18+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
19+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
20+
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
21+
22+
# Okabe-Ito palette (first series is always #009E73)
23+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"]
24+
1225
# Data - use Iris dataset with 4 measurements per flower
1326
np.random.seed(42)
1427
iris = load_iris()
@@ -18,7 +31,6 @@
1831
target_names = iris.target_names
1932

2033
# Select subset for clear visualization (5 samples per species = 15 faces)
21-
# Choose samples with maximum variation within species by selecting spread across range
2234
indices = []
2335
for species in range(3):
2436
species_mask = y == species
@@ -44,9 +56,6 @@
4456
# Normalize data to 0-1 range
4557
X_norm = (X_subset - X_subset.min(axis=0)) / (X_subset.max(axis=0) - X_subset.min(axis=0))
4658

47-
# Colors for each species
48-
colors = ["#306998", "#FFD43B", "#2CA02C"] # Python Blue, Python Yellow, Green
49-
5059
# Create figure
5160
fig = go.Figure()
5261

@@ -66,7 +75,7 @@
6675
cx = col * spacing + spacing / 2
6776
cy = (n_rows - 1 - row) * spacing + spacing / 2
6877

69-
color = colors[species]
78+
color = OKABE_ITO[species]
7079

7180
# Feature mapping with increased variation:
7281
# - sepal_length (data[0]) -> face width
@@ -90,7 +99,7 @@
9099
type="path",
91100
path="M " + " L ".join([f"{x},{y}" for x, y in zip(face_x, face_y)]) + " Z",
92101
fillcolor=color,
93-
line=dict(color="#333333", width=2),
102+
line=dict(color=INK_SOFT, width=2),
94103
opacity=0.35,
95104
)
96105
)
@@ -108,8 +117,8 @@
108117
dict(
109118
type="path",
110119
path="M " + " L ".join([f"{x},{y}" for x, y in zip(left_eye_x, left_eye_y)]) + " Z",
111-
fillcolor="white",
112-
line=dict(color="#333333", width=2),
120+
fillcolor=ELEVATED_BG,
121+
line=dict(color=INK_SOFT, width=2),
113122
)
114123
)
115124

@@ -121,8 +130,8 @@
121130
dict(
122131
type="path",
123132
path="M " + " L ".join([f"{x},{y}" for x, y in zip(pupil_x, pupil_y)]) + " Z",
124-
fillcolor="#333333",
125-
line=dict(color="#333333", width=1),
133+
fillcolor=INK,
134+
line=dict(color=INK, width=1),
126135
)
127136
)
128137

@@ -133,8 +142,8 @@
133142
dict(
134143
type="path",
135144
path="M " + " L ".join([f"{x},{y}" for x, y in zip(right_eye_x, right_eye_y)]) + " Z",
136-
fillcolor="white",
137-
line=dict(color="#333333", width=2),
145+
fillcolor=ELEVATED_BG,
146+
line=dict(color=INK_SOFT, width=2),
138147
)
139148
)
140149

@@ -145,8 +154,8 @@
145154
dict(
146155
type="path",
147156
path="M " + " L ".join([f"{x},{y}" for x, y in zip(pupil_x, pupil_y)]) + " Z",
148-
fillcolor="#333333",
149-
line=dict(color="#333333", width=1),
157+
fillcolor=INK,
158+
line=dict(color=INK, width=1),
150159
)
151160
)
152161

@@ -155,9 +164,7 @@
155164
nose_w = face_w * 0.1
156165
nose_y_center = cy
157166
nose_path = f"M {cx},{nose_y_center + nose_h * 0.5} L {cx - nose_w},{nose_y_center - nose_h * 0.5} L {cx + nose_w},{nose_y_center - nose_h * 0.5} Z"
158-
all_shapes.append(
159-
dict(type="path", path=nose_path, fillcolor="#333333", line=dict(color="#333333", width=1), opacity=0.5)
160-
)
167+
all_shapes.append(dict(type="path", path=nose_path, fillcolor=INK, line=dict(color=INK, width=1), opacity=0.5))
161168

162169
# Mouth (curved line with much more variation)
163170
mouth_y_base = cy - face_h * 0.35
@@ -167,7 +174,7 @@
167174
# Parabolic curve: positive = smile, negative = frown
168175
mouth_y_vals = mouth_y_base + mouth_curve * (1 - ((mouth_x_vals - cx) / mouth_width) ** 2) * radius * 0.5
169176
mouth_path = "M " + " L ".join([f"{x},{y}" for x, y in zip(mouth_x_vals, mouth_y_vals)])
170-
all_shapes.append(dict(type="path", path=mouth_path, line=dict(color="#333333", width=3)))
177+
all_shapes.append(dict(type="path", path=mouth_path, line=dict(color=INK, width=3)))
171178

172179
# Eyebrows (angled based on face width feature)
173180
brow_offset_y = eye_offset_y + eye_r + face_h * 0.1
@@ -182,7 +189,7 @@
182189
y0=cy + brow_offset_y - brow_angle * radius,
183190
x1=cx - eye_offset_x + brow_width,
184191
y1=cy + brow_offset_y + brow_angle * radius,
185-
line=dict(color="#333333", width=3),
192+
line=dict(color=INK, width=3),
186193
)
187194
)
188195

@@ -194,21 +201,21 @@
194201
y0=cy + brow_offset_y + brow_angle * radius,
195202
x1=cx + eye_offset_x + brow_width,
196203
y1=cy + brow_offset_y - brow_angle * radius,
197-
line=dict(color="#333333", width=3),
204+
line=dict(color=INK, width=3),
198205
)
199206
)
200207

201208
# Add invisible scatter for axis setup
202209
fig.add_trace(go.Scatter(x=[0], y=[0], mode="markers", marker=dict(opacity=0), showlegend=False))
203210

204211
# Add legend entries for species
205-
for i, (name, color) in enumerate(zip(target_names, colors)):
212+
for i, (name, color) in enumerate(zip(target_names, OKABE_ITO)):
206213
fig.add_trace(
207214
go.Scatter(
208215
x=[None],
209216
y=[None],
210217
mode="markers",
211-
marker=dict(size=20, color=color, opacity=0.5, line=dict(color="#333333", width=2)),
218+
marker=dict(size=20, color=color, opacity=0.5, line=dict(color=INK_SOFT, width=2)),
212219
name=name.capitalize(),
213220
showlegend=True,
214221
)
@@ -222,19 +229,15 @@
222229
y=row_y,
223230
text=f"<b>{name.capitalize()}</b>",
224231
showarrow=False,
225-
font=dict(size=18, color="#333333"),
232+
font=dict(size=18, color=INK),
226233
xanchor="right",
227234
)
228235

229236
# Column labels (sample numbers)
230237
for col in range(n_cols):
231238
col_x = col * spacing + spacing / 2
232239
fig.add_annotation(
233-
x=col_x,
234-
y=n_rows * spacing + 0.2,
235-
text=f"Sample {col + 1}",
236-
showarrow=False,
237-
font=dict(size=16, color="#666666"),
240+
x=col_x, y=n_rows * spacing + 0.2, text=f"Sample {col + 1}", showarrow=False, font=dict(size=16, color=INK_SOFT)
238241
)
239242

240243
# Feature mapping legend
@@ -250,20 +253,18 @@
250253
y=n_rows * spacing / 2,
251254
text=mapping_text,
252255
showarrow=False,
253-
font=dict(size=14, color="#333333"),
256+
font=dict(size=14, color=INK_SOFT),
254257
align="left",
255258
xanchor="left",
256-
bgcolor="rgba(255,255,255,0.9)",
257-
bordercolor="#cccccc",
259+
bgcolor=ELEVATED_BG,
260+
bordercolor=INK_SOFT,
258261
borderwidth=1,
259262
borderpad=10,
260263
)
261264

262265
# Update layout - optimized for better space utilization
263266
fig.update_layout(
264-
title=dict(
265-
text="chernoff-basic · plotly · pyplots.ai", font=dict(size=28, color="#333333"), x=0.5, xanchor="center"
266-
),
267+
title=dict(text="chernoff-basic · plotly · anyplot.ai", font=dict(size=28, color=INK), x=0.5, xanchor="center"),
267268
shapes=all_shapes,
268269
xaxis=dict(range=[-1.2, n_cols * spacing + 3.0], showgrid=False, zeroline=False, showticklabels=False, title=""),
269270
yaxis=dict(
@@ -275,23 +276,24 @@
275276
scaleanchor="x",
276277
scaleratio=1,
277278
),
278-
template="plotly_white",
279+
paper_bgcolor=PAGE_BG,
280+
plot_bgcolor=PAGE_BG,
281+
font=dict(color=INK),
279282
legend=dict(
280-
title=dict(text="<b>Species</b>", font=dict(size=18)),
281-
font=dict(size=16),
283+
title=dict(text="<b>Species</b>", font=dict(size=18, color=INK)),
284+
font=dict(size=16, color=INK_SOFT),
282285
x=1.02,
283286
y=0.98,
284287
xanchor="left",
285-
bgcolor="rgba(255,255,255,0.9)",
286-
bordercolor="#cccccc",
288+
bgcolor=ELEVATED_BG,
289+
bordercolor=INK_SOFT,
287290
borderwidth=1,
288291
),
289292
margin=dict(l=100, r=180, t=80, b=40),
290-
plot_bgcolor="white",
291293
)
292294

293295
# Save as PNG
294-
fig.write_image("plot.png", width=1600, height=900, scale=3)
296+
fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
295297

296298
# Save interactive HTML
297-
fig.write_html("plot.html", include_plotlyjs=True, full_html=True)
299+
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

0 commit comments

Comments
 (0)