{"spec_id":"chernoff-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nchernoff-basic: Chernoff Faces for Multivariate Data\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom sklearn.datasets import load_iris\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette (first series is always #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data - use Iris dataset with 4 measurements per flower\nnp.random.seed(42)\niris = load_iris()\nX = iris.data\ny = iris.target\nfeature_names = iris.feature_names\ntarget_names = iris.target_names\n\n# Select subset for clear visualization (5 samples per species = 15 faces)\nindices = []\nfor species in range(3):\n    species_mask = y == species\n    species_data = X[species_mask]\n    species_indices_all = np.where(species_mask)[0]\n    # Calculate variance score for each sample and select diverse ones\n    mean_vals = species_data.mean(axis=0)\n    distances = np.sum((species_data - mean_vals) ** 2, axis=1)\n    # Select min, max distance and 3 evenly spaced others\n    sorted_idx = np.argsort(distances)\n    selected = [\n        sorted_idx[0],\n        sorted_idx[len(sorted_idx) // 4],\n        sorted_idx[len(sorted_idx) // 2],\n        sorted_idx[3 * len(sorted_idx) // 4],\n        sorted_idx[-1],\n    ]\n    indices.extend([species_indices_all[i] for i in selected])\n\nX_subset = X[indices]\ny_subset = y[indices]\n\n# Normalize data to 0-1 range\nX_norm = (X_subset - X_subset.min(axis=0)) / (X_subset.max(axis=0) - X_subset.min(axis=0))\n\n# Create figure\nfig = go.Figure()\n\n# Grid layout: 3 rows (species) x 5 columns (samples)\nn_cols = 5\nn_rows = 3\nspacing = 2.2\nradius = 0.95\n\nall_shapes = []\n\n# Create all faces inline (KISS - no functions)\nfor i, (data, species) in enumerate(zip(X_norm, y_subset)):\n    row = i // n_cols\n    col = i % n_cols\n\n    cx = col * spacing + spacing / 2\n    cy = (n_rows - 1 - row) * spacing + spacing / 2\n\n    color = IMPRINT[species]\n\n    # Feature mapping with increased variation:\n    #   - sepal_length (data[0]) -> face width\n    #   - sepal_width (data[1]) -> face height\n    #   - petal_length (data[2]) -> eye size\n    #   - petal_width (data[3]) -> mouth curvature\n    face_width_factor = 0.6 + data[0] * 0.8  # 0.6-1.4 (wider range)\n    face_height_factor = 0.7 + data[1] * 0.6  # 0.7-1.3 (wider range)\n    eye_size = 0.06 + data[2] * 0.18  # 0.06-0.24 (more variation)\n    mouth_curve = -0.3 + data[3] * 0.6  # -0.3 to +0.3 (frown to big smile)\n\n    # Face outline (ellipse)\n    face_w = radius * face_width_factor\n    face_h = radius * face_height_factor\n    theta = np.linspace(0, 2 * np.pi, 50)\n    face_x = cx + face_w * np.cos(theta)\n    face_y = cy + face_h * np.sin(theta)\n\n    all_shapes.append(\n        dict(\n            type=\"path\",\n            path=\"M \" + \" L \".join([f\"{x},{y}\" for x, y in zip(face_x, face_y)]) + \" Z\",\n            fillcolor=color,\n            line=dict(color=INK_SOFT, width=2),\n            opacity=0.35,\n        )\n    )\n\n    # Eyes\n    eye_offset_x = face_w * 0.35\n    eye_offset_y = face_h * 0.22\n    eye_r = radius * eye_size\n    eye_theta = np.linspace(0, 2 * np.pi, 30)\n\n    # Left eye\n    left_eye_x = (cx - eye_offset_x) + eye_r * np.cos(eye_theta)\n    left_eye_y = (cy + eye_offset_y) + eye_r * 0.7 * np.sin(eye_theta)\n    all_shapes.append(\n        dict(\n            type=\"path\",\n            path=\"M \" + \" L \".join([f\"{x},{y}\" for x, y in zip(left_eye_x, left_eye_y)]) + \" Z\",\n            fillcolor=ELEVATED_BG,\n            line=dict(color=INK_SOFT, width=2),\n        )\n    )\n\n    # Left pupil\n    pupil_r = eye_r * 0.5\n    pupil_x = (cx - eye_offset_x) + pupil_r * np.cos(eye_theta)\n    pupil_y = (cy + eye_offset_y) + pupil_r * np.sin(eye_theta)\n    all_shapes.append(\n        dict(\n            type=\"path\",\n            path=\"M \" + \" L \".join([f\"{x},{y}\" for x, y in zip(pupil_x, pupil_y)]) + \" Z\",\n            fillcolor=INK,\n            line=dict(color=INK, width=1),\n        )\n    )\n\n    # Right eye\n    right_eye_x = (cx + eye_offset_x) + eye_r * np.cos(eye_theta)\n    right_eye_y = (cy + eye_offset_y) + eye_r * 0.7 * np.sin(eye_theta)\n    all_shapes.append(\n        dict(\n            type=\"path\",\n            path=\"M \" + \" L \".join([f\"{x},{y}\" for x, y in zip(right_eye_x, right_eye_y)]) + \" Z\",\n            fillcolor=ELEVATED_BG,\n            line=dict(color=INK_SOFT, width=2),\n        )\n    )\n\n    # Right pupil\n    pupil_x = (cx + eye_offset_x) + pupil_r * np.cos(eye_theta)\n    pupil_y = (cy + eye_offset_y) + pupil_r * np.sin(eye_theta)\n    all_shapes.append(\n        dict(\n            type=\"path\",\n            path=\"M \" + \" L \".join([f\"{x},{y}\" for x, y in zip(pupil_x, pupil_y)]) + \" Z\",\n            fillcolor=INK,\n            line=dict(color=INK, width=1),\n        )\n    )\n\n    # Nose (simple triangle)\n    nose_h = face_h * 0.15\n    nose_w = face_w * 0.1\n    nose_y_center = cy\n    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\"\n    all_shapes.append(dict(type=\"path\", path=nose_path, fillcolor=INK, line=dict(color=INK, width=1), opacity=0.5))\n\n    # Mouth (curved line with much more variation)\n    mouth_y_base = cy - face_h * 0.35\n    mouth_width = face_w * 0.5\n    mouth_points = 20\n    mouth_x_vals = np.linspace(cx - mouth_width, cx + mouth_width, mouth_points)\n    # Parabolic curve: positive = smile, negative = frown\n    mouth_y_vals = mouth_y_base + mouth_curve * (1 - ((mouth_x_vals - cx) / mouth_width) ** 2) * radius * 0.5\n    mouth_path = \"M \" + \" L \".join([f\"{x},{y}\" for x, y in zip(mouth_x_vals, mouth_y_vals)])\n    all_shapes.append(dict(type=\"path\", path=mouth_path, line=dict(color=INK, width=3)))\n\n    # Eyebrows (angled based on face width feature)\n    brow_offset_y = eye_offset_y + eye_r + face_h * 0.1\n    brow_width = eye_r * 1.2\n    brow_angle = (data[0] - 0.5) * 0.2  # More angle variation\n\n    # Left eyebrow\n    all_shapes.append(\n        dict(\n            type=\"line\",\n            x0=cx - eye_offset_x - brow_width,\n            y0=cy + brow_offset_y - brow_angle * radius,\n            x1=cx - eye_offset_x + brow_width,\n            y1=cy + brow_offset_y + brow_angle * radius,\n            line=dict(color=INK, width=3),\n        )\n    )\n\n    # Right eyebrow\n    all_shapes.append(\n        dict(\n            type=\"line\",\n            x0=cx + eye_offset_x - brow_width,\n            y0=cy + brow_offset_y + brow_angle * radius,\n            x1=cx + eye_offset_x + brow_width,\n            y1=cy + brow_offset_y - brow_angle * radius,\n            line=dict(color=INK, width=3),\n        )\n    )\n\n# Add invisible scatter for axis setup\nfig.add_trace(go.Scatter(x=[0], y=[0], mode=\"markers\", marker=dict(opacity=0), showlegend=False))\n\n# Add legend entries for species\nfor i, (name, color) in enumerate(zip(target_names, IMPRINT)):\n    fig.add_trace(\n        go.Scatter(\n            x=[None],\n            y=[None],\n            mode=\"markers\",\n            marker=dict(size=20, color=color, opacity=0.5, line=dict(color=INK_SOFT, width=2)),\n            name=name.capitalize(),\n            showlegend=True,\n        )\n    )\n\n# Row labels (species names)\nfor i, name in enumerate(target_names):\n    row_y = (n_rows - 1 - i) * spacing + spacing / 2\n    fig.add_annotation(\n        x=-0.6,\n        y=row_y,\n        text=f\"<b>{name.capitalize()}</b>\",\n        showarrow=False,\n        font=dict(size=18, color=INK),\n        xanchor=\"right\",\n    )\n\n# Column labels (sample numbers)\nfor col in range(n_cols):\n    col_x = col * spacing + spacing / 2\n    fig.add_annotation(\n        x=col_x, y=n_rows * spacing + 0.2, text=f\"Sample {col + 1}\", showarrow=False, font=dict(size=16, color=INK_SOFT)\n    )\n\n# Feature mapping legend\nmapping_text = (\n    \"<b>Feature Mapping:</b><br>\"\n    \"Face Width: Sepal Length<br>\"\n    \"Face Height: Sepal Width<br>\"\n    \"Eye Size: Petal Length<br>\"\n    \"Smile: Petal Width\"\n)\nfig.add_annotation(\n    x=n_cols * spacing + 0.3,\n    y=n_rows * spacing / 2,\n    text=mapping_text,\n    showarrow=False,\n    font=dict(size=14, color=INK_SOFT),\n    align=\"left\",\n    xanchor=\"left\",\n    bgcolor=ELEVATED_BG,\n    bordercolor=INK_SOFT,\n    borderwidth=1,\n    borderpad=10,\n)\n\n# Update layout - optimized for better space utilization\nfig.update_layout(\n    title=dict(text=\"chernoff-basic · plotly · anyplot.ai\", font=dict(size=28, color=INK), x=0.5, xanchor=\"center\"),\n    shapes=all_shapes,\n    xaxis=dict(range=[-1.2, n_cols * spacing + 3.0], showgrid=False, zeroline=False, showticklabels=False, title=\"\"),\n    yaxis=dict(\n        range=[-0.3, n_rows * spacing + 0.6],\n        showgrid=False,\n        zeroline=False,\n        showticklabels=False,\n        title=\"\",\n        scaleanchor=\"x\",\n        scaleratio=1,\n    ),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font=dict(color=INK),\n    legend=dict(\n        title=dict(text=\"<b>Species</b>\", font=dict(size=18, color=INK)),\n        font=dict(size=16, color=INK_SOFT),\n        x=1.02,\n        y=0.98,\n        xanchor=\"left\",\n        bgcolor=ELEVATED_BG,\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n    ),\n    margin=dict(l=100, r=180, t=80, b=40),\n)\n\n# Save as PNG\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\n\n# Save interactive HTML\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}