{"spec_id":"chernoff-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nchernoff-basic: Chernoff Faces for Multivariate Data\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\nimport sys\nimport xml.etree.ElementTree as ET\n\n\n# Add site-packages to the beginning of sys.path to avoid local pygal.py conflict\nsite_packages = None\nfor path in sys.path:\n    if \"site-packages\" in path:\n        site_packages = path\n        break\n\nif site_packages:\n    sys.path.insert(0, site_packages)\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme-adaptive colors\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Okabe-Ito palette (first series is brand green #009E73)\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\")\n\n# Set seed for reproducibility\nnp.random.seed(42)\n\n# Data - Car performance metrics (5 cars, 7 attributes each)\n# Attributes: Engine Power, Fuel Efficiency, Safety Rating, Comfort, Reliability, Price Value, Handling\ncar_names = [\"Sedan A\", \"SUV B\", \"Sports C\", \"Compact D\", \"Luxury E\"]\n\n# Normalized data (0-1 scale) for each car's attributes\n# Each row: [face_width, face_height, eye_size, eye_spacing, mouth_curve, nose_length, eyebrow_slant]\ncar_data = np.array(\n    [\n        [0.6, 0.5, 0.7, 0.5, 0.8, 0.4, 0.5],  # Sedan A - balanced, happy\n        [0.8, 0.7, 0.5, 0.6, 0.4, 0.7, 0.3],  # SUV B - large, serious\n        [0.4, 0.6, 0.9, 0.4, 0.9, 0.3, 0.7],  # Sports C - narrow, excited\n        [0.5, 0.4, 0.6, 0.5, 0.6, 0.5, 0.5],  # Compact D - small, neutral\n        [0.7, 0.8, 0.8, 0.7, 0.7, 0.6, 0.6],  # Luxury E - large, pleasant\n    ]\n)\n\n# Okabe-Ito palette for face colors\nface_colors = IMPRINT\n\n# SVG namespace\nSVG_NS = \"http://www.w3.org/2000/svg\"\nET.register_namespace(\"\", SVG_NS)\n\n# Custom style for pygal\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_SOFT,\n    colors=IMPRINT,\n    title_font_size=72,\n    label_font_size=36,\n    major_label_font_size=32,\n    legend_font_size=36,\n    value_font_size=28,\n)\n\n# Create a base pygal XY chart to leverage its SVG rendering infrastructure\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    show_legend=False,\n    show_x_guides=False,\n    show_y_guides=False,\n    show_x_labels=False,\n    show_y_labels=False,\n    show_dots=False,\n    margin=50,\n    no_data_text=\"\",\n)\n\n# Add dummy data to create valid chart structure (hidden)\nchart.add(\"\", [])\n\n# Render to SVG string\nsvg_string = chart.render().decode(\"utf-8\")\n\n# Parse SVG and add custom Chernoff faces\nsvg_tree = ET.fromstring(svg_string)\n\n# Create faces group\nfaces_group = ET.SubElement(svg_tree, f\"{{{SVG_NS}}}g\")\nfaces_group.set(\"id\", \"chernoff-faces\")\n\n# Calculate face positions in a grid (5 faces in a row)\ncols = 5\nface_size = 520\nmargin_x = 500\nspacing_x = (4800 - 2 * margin_x) / (cols - 1) if cols > 1 else 0\nbase_cy = 950  # Center faces vertically for better canvas utilization\n\n# Draw each Chernoff face inline (KISS structure - no functions)\nfor i, (name, data, color) in enumerate(zip(car_names, car_data, face_colors, strict=True)):\n    col = i % cols\n    cx = margin_x + col * spacing_x\n    cy = base_cy\n\n    # Calculate facial feature parameters from data\n    scale = face_size / 200\n    face_width_factor = 0.6 + data[0] * 0.4\n    face_height_factor = 0.6 + data[1] * 0.4\n    eye_size = (12 + data[2] * 28) * scale\n    eye_spacing = (30 + data[3] * 40) * scale\n    mouth_curve = (-50 + data[4] * 100) * scale\n    nose_length = (20 + data[5] * 35) * scale\n    eyebrow_slant = -20 + data[6] * 40\n\n    face_width = face_size * face_width_factor * 0.45\n    face_height = face_size * face_height_factor * 0.55\n\n    # Create group for this face\n    face_group = ET.SubElement(faces_group, f\"{{{SVG_NS}}}g\")\n    face_group.set(\"id\", f\"face-{name.replace(' ', '-')}\")\n\n    # Face outline (ellipse)\n    face_ellipse = ET.SubElement(face_group, f\"{{{SVG_NS}}}ellipse\")\n    face_ellipse.set(\"cx\", str(cx))\n    face_ellipse.set(\"cy\", str(cy))\n    face_ellipse.set(\"rx\", str(face_width))\n    face_ellipse.set(\"ry\", str(face_height))\n    face_ellipse.set(\"fill\", color)\n    face_ellipse.set(\"fill-opacity\", \"0.3\")\n    face_ellipse.set(\"stroke\", color)\n    face_ellipse.set(\"stroke-width\", str(max(4, 3 * scale)))\n\n    # Left eye\n    left_eye_x = cx - eye_spacing * 0.5\n    left_eye_y = cy - face_height * 0.2\n    left_eye = ET.SubElement(face_group, f\"{{{SVG_NS}}}circle\")\n    left_eye.set(\"cx\", str(left_eye_x))\n    left_eye.set(\"cy\", str(left_eye_y))\n    left_eye.set(\"r\", str(eye_size))\n    left_eye.set(\"fill\", PAGE_BG)\n    left_eye.set(\"stroke\", INK)\n    left_eye.set(\"stroke-width\", str(max(3, 2.5 * scale)))\n\n    # Left pupil\n    left_pupil = ET.SubElement(face_group, f\"{{{SVG_NS}}}circle\")\n    left_pupil.set(\"cx\", str(left_eye_x))\n    left_pupil.set(\"cy\", str(left_eye_y))\n    left_pupil.set(\"r\", str(eye_size * 0.4))\n    left_pupil.set(\"fill\", INK)\n\n    # Right eye\n    right_eye_x = cx + eye_spacing * 0.5\n    right_eye_y = cy - face_height * 0.2\n    right_eye = ET.SubElement(face_group, f\"{{{SVG_NS}}}circle\")\n    right_eye.set(\"cx\", str(right_eye_x))\n    right_eye.set(\"cy\", str(right_eye_y))\n    right_eye.set(\"r\", str(eye_size))\n    right_eye.set(\"fill\", PAGE_BG)\n    right_eye.set(\"stroke\", INK)\n    right_eye.set(\"stroke-width\", str(max(3, 2.5 * scale)))\n\n    # Right pupil\n    right_pupil = ET.SubElement(face_group, f\"{{{SVG_NS}}}circle\")\n    right_pupil.set(\"cx\", str(right_eye_x))\n    right_pupil.set(\"cy\", str(right_eye_y))\n    right_pupil.set(\"r\", str(eye_size * 0.4))\n    right_pupil.set(\"fill\", INK)\n\n    # Eyebrows\n    brow_length = eye_size * 2.2\n    brow_y_offset = eye_size + 20 * scale\n    slant_offset = np.tan(np.radians(eyebrow_slant)) * brow_length * 0.5\n\n    # Left eyebrow\n    left_brow = ET.SubElement(face_group, f\"{{{SVG_NS}}}line\")\n    left_brow.set(\"x1\", str(left_eye_x - brow_length * 0.5))\n    left_brow.set(\"y1\", str(left_eye_y - brow_y_offset + slant_offset))\n    left_brow.set(\"x2\", str(left_eye_x + brow_length * 0.5))\n    left_brow.set(\"y2\", str(left_eye_y - brow_y_offset - slant_offset))\n    left_brow.set(\"stroke\", INK)\n    left_brow.set(\"stroke-width\", str(max(5, 4 * scale)))\n    left_brow.set(\"stroke-linecap\", \"round\")\n\n    # Right eyebrow (mirrored slant)\n    right_brow = ET.SubElement(face_group, f\"{{{SVG_NS}}}line\")\n    right_brow.set(\"x1\", str(right_eye_x - brow_length * 0.5))\n    right_brow.set(\"y1\", str(right_eye_y - brow_y_offset - slant_offset))\n    right_brow.set(\"x2\", str(right_eye_x + brow_length * 0.5))\n    right_brow.set(\"y2\", str(right_eye_y - brow_y_offset + slant_offset))\n    right_brow.set(\"stroke\", INK)\n    right_brow.set(\"stroke-width\", str(max(5, 4 * scale)))\n    right_brow.set(\"stroke-linecap\", \"round\")\n\n    # Nose (vertical line)\n    nose = ET.SubElement(face_group, f\"{{{SVG_NS}}}line\")\n    nose.set(\"x1\", str(cx))\n    nose.set(\"y1\", str(cy - nose_length * 0.3))\n    nose.set(\"x2\", str(cx))\n    nose.set(\"y2\", str(cy + nose_length * 0.5))\n    nose.set(\"stroke\", INK)\n    nose.set(\"stroke-width\", str(max(4, 3.5 * scale)))\n    nose.set(\"stroke-linecap\", \"round\")\n\n    # Mouth (quadratic bezier curve)\n    mouth_y = cy + face_height * 0.45\n    mouth_width = face_width * 0.55\n    mouth_path = f\"M {cx - mouth_width} {mouth_y} Q {cx} {mouth_y + mouth_curve} {cx + mouth_width} {mouth_y}\"\n    mouth = ET.SubElement(face_group, f\"{{{SVG_NS}}}path\")\n    mouth.set(\"d\", mouth_path)\n    mouth.set(\"fill\", \"none\")\n    mouth.set(\"stroke\", INK)\n    mouth.set(\"stroke-width\", str(max(5, 4 * scale)))\n    mouth.set(\"stroke-linecap\", \"round\")\n\n    # Label below face\n    label_elem = ET.SubElement(face_group, f\"{{{SVG_NS}}}text\")\n    label_elem.set(\"x\", str(cx))\n    label_elem.set(\"y\", str(cy + face_height + 65))\n    label_elem.set(\"text-anchor\", \"middle\")\n    label_elem.set(\"font-family\", \"sans-serif\")\n    label_elem.set(\"font-size\", str(max(36, 28 * scale)))\n    label_elem.set(\"font-weight\", \"bold\")\n    label_elem.set(\"fill\", INK)\n    label_elem.text = name\n\n# Add title\ntitle_elem = ET.SubElement(svg_tree, f\"{{{SVG_NS}}}text\")\ntitle_elem.set(\"x\", \"2400\")\ntitle_elem.set(\"y\", \"100\")\ntitle_elem.set(\"text-anchor\", \"middle\")\ntitle_elem.set(\"font-family\", \"sans-serif\")\ntitle_elem.set(\"font-size\", \"72\")\ntitle_elem.set(\"font-weight\", \"bold\")\ntitle_elem.set(\"fill\", INK)\ntitle_elem.text = \"Car Performance Comparison · chernoff-basic · pygal · pyplots.ai\"\n\n# Add legend for attributes - positioned closer to faces\nlegend_y = 1680\nlegend_x_start = 400\n\nlegend_title = ET.SubElement(svg_tree, f\"{{{SVG_NS}}}text\")\nlegend_title.set(\"x\", str(legend_x_start))\nlegend_title.set(\"y\", str(legend_y))\nlegend_title.set(\"font-family\", \"sans-serif\")\nlegend_title.set(\"font-size\", \"44\")\nlegend_title.set(\"font-weight\", \"bold\")\nlegend_title.set(\"fill\", INK)\nlegend_title.text = \"Feature Mappings:\"\n\nfeature_mappings = [\n    \"Face Width = Engine Power\",\n    \"Face Height = Fuel Efficiency\",\n    \"Eye Size = Safety Rating\",\n    \"Eye Spacing = Comfort\",\n    \"Mouth Curve = Reliability\",\n    \"Nose Length = Price Value\",\n    \"Eyebrow Slant = Handling\",\n]\n\n# Add feature mappings as legend items (two rows)\nfor i, mapping in enumerate(feature_mappings):\n    row = i // 4\n    col = i % 4\n    text_elem = ET.SubElement(svg_tree, f\"{{{SVG_NS}}}text\")\n    text_elem.set(\"x\", str(legend_x_start + col * 1150))\n    text_elem.set(\"y\", str(legend_y + 80 + row * 70))\n    text_elem.set(\"font-family\", \"sans-serif\")\n    text_elem.set(\"font-size\", \"36\")\n    text_elem.set(\"fill\", INK_SOFT)\n    text_elem.text = mapping\n\n# Add color legend for car identification\ncolor_legend_y = legend_y + 230\ncolor_legend_x = 400\n\ncolor_title = ET.SubElement(svg_tree, f\"{{{SVG_NS}}}text\")\ncolor_title.set(\"x\", str(color_legend_x))\ncolor_title.set(\"y\", str(color_legend_y))\ncolor_title.set(\"font-family\", \"sans-serif\")\ncolor_title.set(\"font-size\", \"44\")\ncolor_title.set(\"font-weight\", \"bold\")\ncolor_title.set(\"fill\", INK)\ncolor_title.text = \"Cars:\"\n\nfor i, (name, color) in enumerate(zip(car_names, face_colors, strict=True)):\n    # Color swatch\n    swatch = ET.SubElement(svg_tree, f\"{{{SVG_NS}}}rect\")\n    swatch.set(\"x\", str(color_legend_x + 150 + i * 850))\n    swatch.set(\"y\", str(color_legend_y - 30))\n    swatch.set(\"width\", \"40\")\n    swatch.set(\"height\", \"40\")\n    swatch.set(\"fill\", color)\n    swatch.set(\"rx\", \"5\")\n\n    # Car name\n    name_elem = ET.SubElement(svg_tree, f\"{{{SVG_NS}}}text\")\n    name_elem.set(\"x\", str(color_legend_x + 200 + i * 850))\n    name_elem.set(\"y\", str(color_legend_y))\n    name_elem.set(\"font-family\", \"sans-serif\")\n    name_elem.set(\"font-size\", \"36\")\n    name_elem.set(\"fill\", INK_SOFT)\n    name_elem.text = name\n\n# Convert back to string\nfinal_svg = ET.tostring(svg_tree, encoding=\"unicode\")\n\n# Add XML declaration\nfinal_svg = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' + final_svg\n\n# Save as SVG file\nwith open(f\"plot-{THEME}.svg\", \"w\") as f:\n    f.write(final_svg)\n\n# Use cairosvg to convert to PNG\ncairosvg.svg2png(bytestring=final_svg.encode(), write_to=f\"plot-{THEME}.png\", output_width=4800, output_height=2700)\n\n# Save HTML for interactive version\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(\n        f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <title>chernoff-basic · pygal · pyplots.ai</title>\n    <style>\n        body {{ margin: 0; padding: 20px; background: {PAGE_BG}; font-family: sans-serif; }}\n        .container {{ max-width: 100%; margin: 0 auto; }}\n        h1 {{ text-align: center; color: {INK}; }}\n        object {{ width: 100%; height: auto; }}\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <object type=\"image/svg+xml\" data=\"plot-{THEME}.svg\">\n            Chernoff faces visualization not supported\n        </object>\n    </div>\n</body>\n</html>\"\"\"\n    )\n"}