{"spec_id":"venn-labeled-items","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nvenn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 84/100 | Updated: 2026-06-25\n\"\"\"\n\nimport importlib\nimport math\nimport os\nimport re\nimport sys\nfrom collections import defaultdict\n\n\n# Drop the script directory from sys.path so the `pygal` package resolves, not this file\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\npygal = importlib.import_module(\"pygal\")\nStyle = importlib.import_module(\"pygal.style\").Style\ncairosvg = importlib.import_module(\"cairosvg\")\n\n\n# Theme tokens\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# Imprint palette — positions 1–3\nCOLOR_A = \"#009E73\"  # brand green\nCOLOR_B = \"#C475FD\"  # lavender\nCOLOR_C = \"#4467A3\"  # blue\n\n# Symmetric three-circle Venn: equilateral triangle of centers (apex up)\nRADIUS = 1.0\nOFFSET = RADIUS / math.sqrt(3)\nax, ay = -OFFSET * math.sin(math.radians(60)), -OFFSET * math.cos(math.radians(60))\nbx, by = OFFSET * math.sin(math.radians(60)), -OFFSET * math.cos(math.radians(60))\ncx, cy = 0.0, OFFSET\n\ncircles = [\n    {\n        \"name\": \"TREND REPORT\",\n        \"color\": COLOR_A,\n        \"center\": (ax, ay),\n        \"label_xy\": (ax - 0.90, ay - 1.05),\n        \"anchor\": \"start\",\n    },\n    {\n        \"name\": \"WARDROBE STAPLE\",\n        \"color\": COLOR_B,\n        \"center\": (bx, by),\n        \"label_xy\": (bx + 0.90, by - 1.05),\n        \"anchor\": \"end\",\n    },\n    {\"name\": \"GUILTY CLOSET\", \"color\": COLOR_C, \"center\": (cx, cy), \"label_xy\": (cx, cy + 1.12), \"anchor\": \"middle\"},\n]\n\n# Fashion micro-trends distributed across the seven interior zones\nitems_raw = [\n    (\"Shackets\", \"A\"),\n    (\"Digital Fashion\", \"A\"),\n    (\"Micro Bags\", \"A\"),\n    (\"White Sneakers\", \"B\"),\n    (\"Trench Coats\", \"B\"),\n    (\"Good Denim\", \"B\"),\n    (\"Fast Fashion\", \"C\"),\n    (\"Matching Sets\", \"C\"),\n    (\"Oversized Blazers\", \"AB\"),\n    (\"Straight-Leg Jeans\", \"AB\"),\n    (\"Cottagecore\", \"AC\"),\n    (\"Tie-Dye\", \"AC\"),\n    (\"Minimalist Sneakers\", \"BC\"),\n    (\"Linen Separates\", \"BC\"),\n    (\"Quiet Luxury\", \"ABC\"),\n    (\"Ballet Flats\", \"ABC\"),\n]\n\n# ABC zone items that receive bold emphasis (most editorially interesting intersection)\nABC_ITEMS = {\"Quiet Luxury\", \"Ballet Flats\"}\n\n# Centroids of each Venn region in chart-data units\nzone_centers = {\n    \"A\": (ax - 0.52, ay + 0.05),\n    \"B\": (bx + 0.52, by + 0.05),\n    \"C\": (cx, cy + 0.48),\n    \"AB\": (0.0, by - 0.30),\n    \"AC\": (-0.43, 0.18),\n    \"BC\": (0.43, 0.18),\n    \"ABC\": (0.0, -0.05),\n}\n\nLINE_HEIGHT = 0.13\nzone_to_items = defaultdict(list)\nfor label, zone in items_raw:\n    zone_to_items[zone].append(label)\n\nitem_points = []\nfor zone, labels in zone_to_items.items():\n    zx, zy = zone_centers[zone]\n    n = len(labels)\n    start_y = zy + (n - 1) * LINE_HEIGHT / 2\n    for i, label in enumerate(labels):\n        item_points.append({\"value\": (zx, start_y - i * LINE_HEIGHT), \"label\": label})\n\n\ndef circle_outline(center, r, n=120):\n    cx0, cy0 = center\n    return [(cx0 + r * math.cos(2 * math.pi * i / n), cy0 + r * math.sin(2 * math.pi * i / n)) for i in range(n + 1)]\n\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(COLOR_A, COLOR_B, COLOR_C, INK, INK, INK, INK),\n    opacity=\"1\",\n    opacity_hover=\"1\",\n    stroke_width=5,\n    stroke_opacity=\".90\",\n    stroke_opacity_hover=\".90\",\n    title_font_size=50,\n    label_font_size=22,\n    major_label_font_size=22,\n    legend_font_size=22,\n    value_font_size=52,\n    value_label_font_size=52,\n    title_font_family=\"serif\",\n    label_font_family=\"serif\",\n    major_label_font_family=\"serif\",\n    legend_font_family=\"serif\",\n    value_font_family=\"serif\",\n    value_label_font_family=\"serif\",\n    transition=\"0\",\n)\n\n# Canvas: 2400×2400 (square) — canonical pygal square size; tighter range fills more canvas\nchart = pygal.XY(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=\"Fashion Micro-Trends 2026 · venn-labeled-items · python · pygal · anyplot.ai\",\n    show_legend=False,\n    show_x_labels=False,\n    show_y_labels=False,\n    show_x_guides=False,\n    show_y_guides=False,\n    show_minor_x_labels=False,\n    show_minor_y_labels=False,\n    xrange=(-2.0, 2.0),\n    range=(-2.0, 2.0),\n    margin=20,\n    spacing=0,\n    show_dots=True,\n    dots_size=0,\n    print_labels=True,\n    print_values=False,\n    pretty_print=True,\n)\n\n# Three circle outlines (one series per circle) — fills added via SVG post-processing\nfor c in circles:\n    chart.add(\"\", circle_outline(c[\"center\"], RADIUS), stroke=True, fill=False, show_dots=False)\n\n# Item names — text-only placement at zone centroids\nchart.add(\"Items\", item_points, stroke=False, show_dots=True)\n\n# Category names — restyled by post-processor below\nfor c in circles:\n    chart.add(\"\", [{\"value\": c[\"label_xy\"], \"label\": c[\"name\"]}], stroke=False, show_dots=True)\n\nsvg = chart.render().decode(\"utf-8\")\n\n\n# Post-process: pygal cannot natively fill a closed polyline, so we patch the SVG directly.\ndef fill_circle_path(svg_text, serie_idx, color, opacity):\n    pattern = re.compile(\n        r'(<g class=\"series serie-' + str(serie_idx) + r' color-\\d+\">\\s*<path[^>]*?)class=\"line reactive nofill\"'\n    )\n    return pattern.sub(\n        r'\\1class=\"line reactive\" style=\"fill:' + color + \";fill-opacity:\" + str(opacity) + r';stroke-width:6\"',\n        svg_text,\n        count=1,\n    )\n\n\nfor idx, c in enumerate(circles):\n    svg = fill_circle_path(svg, idx, c[\"color\"], 0.18)\n\n\ndef restyle_label(svg_text, label_text, color, anchor, font_size):\n    \"\"\"Apply bold italic colored style to a category name label element.\"\"\"\n    pattern = re.compile(\n        r\"<text(\\s+x=\\\"[^\\\"]*\\\"\\s+y=\\\"[^\\\"]*\\\")\\s+class=\\\"label\\\">\" + re.escape(label_text) + r\"</text>\"\n    )\n    repl = (\n        r'<text\\1 class=\"label\" style=\"font-size:'\n        + str(font_size)\n        + \";font-style:italic;font-weight:bold;text-anchor:\"\n        + anchor\n        + \";fill:\"\n        + color\n        + '\">'\n        + label_text\n        + \"</text>\"\n    )\n    return pattern.sub(repl, svg_text, count=1)\n\n\nfor c in circles:\n    svg = restyle_label(svg, c[\"name\"], c[\"color\"], c[\"anchor\"], 56)\n\n# Pygal auto-assigns white text for dark series colors — rewrite so labels use INK instead\nsvg = re.sub(r\"(\\.text-overlay \\.color-\\d+ text \\{\\s*fill:\\s*)[^;}\\s]+\", r\"\\1\" + INK, svg)\n# Bump item label size from pygal's hardcoded 36px to the target 52px\nsvg = re.sub(r\"(\\.text-overlay text\\.label \\{[^}]*font-size:\\s*)\\d+px\", r\"\\g<1>52px\", svg)\n\n\ndef emphasize_abc(svg_text, item_label):\n    \"\"\"Bold ABC triple-intersection items to visually distinguish the most interesting zone.\"\"\"\n    return re.sub(\n        r\"(<text)(\\s+x=\\\"[^\\\"]*\\\"\\s+y=\\\"[^\\\"]*\\\"\\s+class=\\\"label\\\">)(\" + re.escape(item_label) + r\"</text>)\",\n        r'\\1 style=\"font-weight:bold;font-size:60px\"\\2\\3',\n        svg_text,\n    )\n\n\nfor item in ABC_ITEMS:\n    svg = emphasize_abc(svg, item)\n\n# Editorial subtitle injected at a fixed canvas position, theme-aware\nsubtitle = (\n    '<g class=\"anyplot-subtitle\"><text x=\"1200\" y=\"2360\" '\n    'style=\"font-family:serif;font-style:italic;font-size:38px;fill:' + INK_SOFT + ';text-anchor:middle\">'\n    \"Sixteen micro-trends, three wardrobe moods, and the truth in the overlap\"\n    \"</text></g>\"\n)\nsvg = svg.replace(\"</svg>\", subtitle + \"</svg>\")\n\n# Save — interactive SVG embedded in HTML, plus rasterized PNG via cairosvg\nwith open(f\"plot-{THEME}.svg\", \"w\") as f:\n    f.write(svg)\n\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(\"<!doctype html><html><body style='margin:0;background:\" + PAGE_BG + \"'>\" + svg + \"</body></html>\")\n\ncairosvg.svg2png(bytestring=svg.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\", output_width=2400, output_height=2400)\n"}