1- """ pyplots .ai
1+ """ anyplot .ai
22chernoff-basic: Chernoff Faces for Multivariate Data
3- Library: highcharts unknown | Python 3.13.11
4- Quality: 91 /100 | Created: 2025-12-31
3+ Library: highcharts unknown | Python 3.13.13
4+ Quality: 80 /100 | Updated: 2026-05-15
55"""
66
77import base64
8+ import os
89import tempfile
910import time
1011from pathlib import Path
1516from sklearn .datasets import load_iris
1617
1718
18- # Data - Using Iris dataset (4 variables per flower)
19+ THEME = os .getenv ("ANYPLOT_THEME" , "light" )
20+ PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
21+ ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
22+ INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
23+ INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
24+ INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
25+
26+ OKABE_ITO = ["#009E73" , "#D55E00" , "#0072B2" ]
27+
28+ # Data
1929np .random .seed (42 )
2030iris = load_iris ()
2131X = iris .data
3646X_max = X_sample .max (axis = 0 )
3747X_norm = (X_sample - X_min ) / (X_max - X_min + 1e-8 )
3848
39- # Colors for species - colorblind-safe
40- species_colors = ["#306998" , "#FFD43B" , "#9467BD" ]
41-
4249# SVG dimensions
4350svg_width = 4800
4451svg_height = 2700
4552
46- # Build custom Chernoff faces HTML/SVG
47- # Feature mappings:
48- # - Variable 0 (sepal length): face width
49- # - Variable 1 (sepal width): eye size
50- # - Variable 2 (petal length): mouth curvature
51- # - Variable 3 (petal width): eyebrow slant
52-
5353
5454def create_face_svg (values , color , label , x_pos , y_pos , size = 450 ):
5555 """Create SVG for a single Chernoff face."""
56- # Extract normalized values (0-1)
57- face_width = 0.7 + values [0 ] * 0.3 # 0.7 to 1.0 multiplier
58- eye_size = 0.7 + values [1 ] * 0.5 # 0.7 to 1.2 multiplier
59- mouth_curve = values [2 ] * 2 - 1 # -1 to 1 (sad to happy)
60- eyebrow_slant = (values [3 ] - 0.5 ) * 30 # -15 to +15 degrees
56+ face_width = 0.7 + values [0 ] * 0.3
57+ eye_size = 0.7 + values [1 ] * 0.5
58+ mouth_curve = values [2 ] * 2 - 1
59+ eyebrow_slant = (values [3 ] - 0.5 ) * 30
6160
6261 cx = x_pos + size // 2
6362 cy = y_pos + size // 2
6463 face_rx = int (size * 0.4 * face_width )
6564 face_ry = int (size * 0.45 )
6665
67- # Eye positions and sizes
6866 eye_cx_left = cx - int (size * 0.15 )
6967 eye_cx_right = cx + int (size * 0.15 )
7068 eye_cy = cy - int (size * 0.08 )
7169 eye_r = int (18 * eye_size )
7270 pupil_r = int (9 * eye_size )
7371
74- # Mouth (cubic bezier curve)
7572 mouth_y = cy + int (size * 0.2 )
7673 mouth_width = int (size * 0.25 )
7774 mouth_curve_offset = int (mouth_curve * size * 0.12 )
7875
79- # Eyebrows
8076 brow_y = eye_cy - int (size * 0.12 )
8177 brow_len = int (size * 0.12 )
8278
8379 svg = f"""
8480 <!-- Face { label } -->
8581 <ellipse cx="{ cx } " cy="{ cy } " rx="{ face_rx } " ry="{ face_ry } "
86- fill="{ color } " stroke="#333333 " stroke-width="4"/>
82+ fill="{ color } " stroke="{ INK } " stroke-width="4"/>
8783
8884 <!-- Left eye -->
89- <circle cx="{ eye_cx_left } " cy="{ eye_cy } " r="{ eye_r } " fill="white " stroke="#333333 " stroke-width="3"/>
90- <circle cx="{ eye_cx_left } " cy="{ eye_cy } " r="{ pupil_r } " fill="#333333 "/>
85+ <circle cx="{ eye_cx_left } " cy="{ eye_cy } " r="{ eye_r } " fill="{ ELEVATED_BG } " stroke="{ INK } " stroke-width="3"/>
86+ <circle cx="{ eye_cx_left } " cy="{ eye_cy } " r="{ pupil_r } " fill="{ INK } "/>
9187
9288 <!-- Right eye -->
93- <circle cx="{ eye_cx_right } " cy="{ eye_cy } " r="{ eye_r } " fill="white " stroke="#333333 " stroke-width="3"/>
94- <circle cx="{ eye_cx_right } " cy="{ eye_cy } " r="{ pupil_r } " fill="#333333 "/>
89+ <circle cx="{ eye_cx_right } " cy="{ eye_cy } " r="{ eye_r } " fill="{ ELEVATED_BG } " stroke="{ INK } " stroke-width="3"/>
90+ <circle cx="{ eye_cx_right } " cy="{ eye_cy } " r="{ pupil_r } " fill="{ INK } "/>
9591
9692 <!-- Left eyebrow -->
9793 <line x1="{ eye_cx_left - brow_len } " y1="{ brow_y + int (eyebrow_slant )} "
9894 x2="{ eye_cx_left + brow_len } " y2="{ brow_y - int (eyebrow_slant )} "
99- stroke="#333333 " stroke-width="5" stroke-linecap="round"/>
95+ stroke="{ INK } " stroke-width="5" stroke-linecap="round"/>
10096
10197 <!-- Right eyebrow -->
10298 <line x1="{ eye_cx_right - brow_len } " y1="{ brow_y - int (eyebrow_slant )} "
10399 x2="{ eye_cx_right + brow_len } " y2="{ brow_y + int (eyebrow_slant )} "
104- stroke="#333333 " stroke-width="5" stroke-linecap="round"/>
100+ stroke="{ INK } " stroke-width="5" stroke-linecap="round"/>
105101
106102 <!-- Nose -->
107103 <line x1="{ cx } " y1="{ cy - int (size * 0.02 )} " x2="{ cx } " y2="{ cy + int (size * 0.1 )} "
108- stroke="#333333 " stroke-width="4" stroke-linecap="round"/>
104+ stroke="{ INK } " stroke-width="4" stroke-linecap="round"/>
109105
110106 <!-- Mouth -->
111107 <path d="M { cx - mouth_width } { mouth_y } Q { cx } { mouth_y + mouth_curve_offset } { cx + mouth_width } { mouth_y } "
112- fill="none" stroke="#333333 " stroke-width="5" stroke-linecap="round"/>
108+ fill="none" stroke="{ INK } " stroke-width="5" stroke-linecap="round"/>
113109
114110 <!-- Label -->
115111 <text x="{ cx } " y="{ y_pos + size + 50 } " text-anchor="middle"
116- font-size="36" font-family="Arial, sans-serif" font-weight="bold">{ label } </text>
112+ font-size="36" font-family="Arial, sans-serif" font-weight="bold" fill=" { INK } " >{ label } </text>
117113 """
118114 return svg
119115
120116
121- # Create the complete HTML with embedded SVG
117+ # Create faces grid
122118faces_svg = ""
123119face_size = 580
124120cols = 3
125121rows = 3
126122
127- # Calculate grid to center faces properly across the canvas
128- # Leave space for legends on right (about 600px) and title at top (about 220px)
129123grid_left = 100
130124grid_right = 3350
131125grid_top = 250
@@ -134,52 +128,50 @@ def create_face_svg(values, color, label, x_pos, y_pos, size=450):
134128grid_width = grid_right - grid_left
135129grid_height = grid_bottom - grid_top
136130
137- # Calculate cell size for even distribution
138131cell_width = grid_width // cols
139132cell_height = grid_height // rows
140133
141134for idx in range (9 ):
142135 row = idx // cols
143136 col = idx % cols
144137
145- # Center face within its cell
146138 cell_x = grid_left + col * cell_width
147139 cell_y = grid_top + row * cell_height
148140 x_pos = cell_x + (cell_width - face_size ) // 2
149- y_pos = cell_y + (cell_height - face_size - 60 ) // 2 # -60 for label space
141+ y_pos = cell_y + (cell_height - face_size - 60 ) // 2
150142
151143 species_idx = y_sample [idx ]
152- color = species_colors [species_idx ]
144+ color = OKABE_ITO [species_idx ]
153145 label = f"{ species_names [species_idx ]} #{ (idx % 3 ) + 1 } "
154146
155147 faces_svg += create_face_svg (X_norm [idx ], color , label , x_pos , y_pos , face_size )
156148
157- # Create legend - positioned in right column, vertically centered
149+ # Species legend
158150legend_x = 3550
159151legend_y = 450
160152legend_svg = f"""
161- <rect x="{ legend_x } " y="{ legend_y } " width="550" height="380" fill="#f8f8f8 " stroke="#333333 " stroke-width="3" rx="15"/>
162- <text x="{ legend_x + 35 } " y="{ legend_y + 60 } " font-size="44" font-family="Arial, sans-serif" font-weight="bold">Species Legend </text>
153+ <rect x="{ legend_x } " y="{ legend_y } " width="550" height="380" fill="{ ELEVATED_BG } " stroke="{ INK_SOFT } " stroke-width="3" rx="15"/>
154+ <text x="{ legend_x + 35 } " y="{ legend_y + 60 } " font-size="44" font-family="Arial, sans-serif" font-weight="bold" fill=" { INK } " >Species</text>
163155"""
164156
165- for i , (species , color ) in enumerate (zip (species_names , species_colors , strict = True )):
157+ for i , (species , color ) in enumerate (zip (species_names , OKABE_ITO , strict = True )):
166158 ly_item = legend_y + 130 + i * 80
167159 legend_svg += f"""
168- <circle cx="{ legend_x + 60 } " cy="{ ly_item } " r="30" fill="{ color } " stroke="#333333 " stroke-width="3"/>
169- <text x="{ legend_x + 110 } " y="{ ly_item + 14 } " font-size="38" font-family="Arial, sans-serif">{ species } </text>
160+ <circle cx="{ legend_x + 60 } " cy="{ ly_item } " r="30" fill="{ color } " stroke="{ INK } " stroke-width="3"/>
161+ <text x="{ legend_x + 110 } " y="{ ly_item + 14 } " font-size="38" font-family="Arial, sans-serif" fill=" { INK } " >{ species } </text>
170162 """
171163
172164# Feature mapping legend
173165feature_legend_y = legend_y + 450
174166feature_legend_svg = f"""
175- <rect x="{ legend_x } " y="{ feature_legend_y } " width="550" height="480" fill="#f8f8f8 " stroke="#333333 " stroke-width="3" rx="15"/>
176- <text x="{ legend_x + 35 } " y="{ feature_legend_y + 60 } " font-size="40" font-family="Arial, sans-serif" font-weight="bold">Feature Mapping</text>
177- <text x="{ legend_x + 35 } " y="{ feature_legend_y + 130 } " font-size="30" font-family="Arial, sans-serif">Face Width → Sepal Length </text>
178- <text x="{ legend_x + 35 } " y="{ feature_legend_y + 190 } " font-size="30" font-family="Arial, sans-serif">Eye Size → Sepal Width </text>
179- <text x="{ legend_x + 35 } " y="{ feature_legend_y + 250 } " font-size="30" font-family="Arial, sans-serif">Mouth Curve → Petal Length </text>
180- <text x="{ legend_x + 35 } " y="{ feature_legend_y + 310 } " font-size="30" font-family="Arial, sans-serif">Eyebrow Slant → Petal Width </text>
181- <line x1="{ legend_x + 35 } " y1="{ feature_legend_y + 355 } " x2="{ legend_x + 515 } " y2="{ feature_legend_y + 355 } " stroke="#cccccc " stroke-width="2"/>
182- <text x="{ legend_x + 35 } " y="{ feature_legend_y + 410 } " font-size="26" font-family="Arial, sans-serif" fill="#666666">All values normalized to 0-1 range </text>
167+ <rect x="{ legend_x } " y="{ feature_legend_y } " width="550" height="480" fill="{ ELEVATED_BG } " stroke="{ INK_SOFT } " stroke-width="3" rx="15"/>
168+ <text x="{ legend_x + 35 } " y="{ feature_legend_y + 60 } " font-size="40" font-family="Arial, sans-serif" font-weight="bold" fill=" { INK } "> Mapping</text>
169+ <text x="{ legend_x + 35 } " y="{ feature_legend_y + 130 } " font-size="30" font-family="Arial, sans-serif" fill=" { INK_SOFT } " >Face W → Sepal L </text>
170+ <text x="{ legend_x + 35 } " y="{ feature_legend_y + 190 } " font-size="30" font-family="Arial, sans-serif" fill=" { INK_SOFT } " >Eye Size → Sepal W </text>
171+ <text x="{ legend_x + 35 } " y="{ feature_legend_y + 250 } " font-size="30" font-family="Arial, sans-serif" fill=" { INK_SOFT } " >Mouth → Petal L </text>
172+ <text x="{ legend_x + 35 } " y="{ feature_legend_y + 310 } " font-size="30" font-family="Arial, sans-serif" fill=" { INK_SOFT } ">Brow → Petal W </text>
173+ <line x1="{ legend_x + 35 } " y1="{ feature_legend_y + 355 } " x2="{ legend_x + 515 } " y2="{ feature_legend_y + 355 } " stroke="{ INK_MUTED } " stroke-width="2"/>
174+ <text x="{ legend_x + 35 } " y="{ feature_legend_y + 410 } " font-size="26" font-family="Arial, sans-serif" fill="{ INK_MUTED } ">Normalized 0–1 </text>
183175"""
184176
185177# Complete HTML
@@ -188,24 +180,24 @@ def create_face_svg(values, color, label, x_pos, y_pos, size=450):
188180<head>
189181 <meta charset="utf-8">
190182 <style>
191- body {{ margin: 0; padding: 0; background: #ffffff ; }}
183+ body {{ margin: 0; padding: 0; background: { PAGE_BG } ; }}
192184 </style>
193185</head>
194186<body>
195187 <svg width="{ svg_width } " height="{ svg_height } " xmlns="http://www.w3.org/2000/svg">
196188 <!-- Background -->
197- <rect width="100%" height="100%" fill="#ffffff "/>
189+ <rect width="100%" height="100%" fill="{ PAGE_BG } "/>
198190
199191 <!-- Title -->
200192 <text x="{ svg_width // 2 } " y="100" text-anchor="middle"
201- font-size="64" font-family="Arial, sans-serif" font-weight="bold">
202- chernoff-basic · highcharts · pyplots .ai
193+ font-size="64" font-family="Arial, sans-serif" font-weight="bold" fill=" { INK } " >
194+ chernoff-basic · highcharts · anyplot .ai
203195 </text>
204196
205197 <!-- Subtitle -->
206198 <text x="{ svg_width // 2 } " y="175" text-anchor="middle"
207- font-size="40" font-family="Arial, sans-serif" fill="#666666 ">
208- Iris Dataset: 4 Variables Mapped to Facial Features (9 Samples, 3 Per Species)
199+ font-size="40" font-family="Arial, sans-serif" fill="{ INK_SOFT } ">
200+ Iris Dataset: 4 Variables Mapped to Facial Features
209201 </text>
210202
211203 <!-- Faces Grid -->
@@ -221,7 +213,7 @@ def create_face_svg(values, color, label, x_pos, y_pos, size=450):
221213</html>"""
222214
223215# Save HTML version
224- with open ("plot.html" , "w" , encoding = "utf-8" ) as f :
216+ with open (f "plot- { THEME } .html" , "w" , encoding = "utf-8" ) as f :
225217 f .write (html_content )
226218
227219# Export to PNG via Selenium
@@ -241,13 +233,11 @@ def create_face_svg(values, color, label, x_pos, y_pos, size=450):
241233driver .get (f"file://{ temp_path } " )
242234time .sleep (3 )
243235
244- # Use CDP to capture full page at exact dimensions
245236driver .execute_cdp_cmd (
246237 "Emulation.setDeviceMetricsOverride" , {"width" : 4800 , "height" : 2700 , "deviceScaleFactor" : 1 , "mobile" : False }
247238)
248239time .sleep (1 )
249240
250- # Take screenshot with clip to exact dimensions
251241result = driver .execute_cdp_cmd (
252242 "Page.captureScreenshot" ,
253243 {
@@ -257,7 +247,7 @@ def create_face_svg(values, color, label, x_pos, y_pos, size=450):
257247 },
258248)
259249
260- with open ("plot.png" , "wb" ) as f :
250+ with open (f "plot- { THEME } .png" , "wb" ) as f :
261251 f .write (base64 .b64decode (result ["data" ]))
262252
263253driver .quit ()
0 commit comments