{"spec_id":"upset-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nupset-basic: UpSet Plot for Multi-Set Intersection Analysis\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 74/100 | Created: 2026-05-13\n\"\"\"\n\nimport os\nfrom collections import Counter\n\nimport cairosvg\nimport numpy as np\n\n\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\"\nBRAND = \"#009E73\"\nPALETTE = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data: Bug report overlap across software modules\nnp.random.seed(42)\nset_names = [\"Frontend\", \"Backend\", \"Database\", \"API Layer\", \"Auth Module\"]\nN_SETS = len(set_names)\nN_BUGS = 700\nprobs = [0.40, 0.35, 0.28, 0.33, 0.22]\n\nmemberships = np.column_stack([np.random.rand(N_BUGS) < p for p in probs])\nmemberships = memberships[memberships.any(axis=1)]\n\nintersection_counts = Counter(frozenset(np.where(row)[0]) for row in memberships)\ntop_n = 10\ntop_intersections = sorted(intersection_counts.items(), key=lambda x: -x[1])[:top_n]\nN_INTS = len(top_intersections)\nset_sizes = memberships.sum(axis=0).astype(int)\nmax_int = top_intersections[0][1]\nmax_set = int(max(set_sizes))\n\n# Canvas and layout\nW, H = 4800, 2700\nML = 60\nLW = 400\nG1 = 60\nSBW = 500\nG2 = 80\nMX0 = ML + LW + G1 + SBW + G2  # matrix x start = 1100\nCW = (W - MX0 - 80) / N_INTS  # column width ≈ 362\n\nAY = 1100  # bar chart baseline y\nBMH = 880  # bar max height\nMT = AY + 100  # matrix top = 1200\nRH = (H - MT - 180) / N_SETS  # row height ≈ 264\nMB = MT + N_SETS * RH  # matrix bottom ≈ 2520\nDR = min(CW * 0.28, RH * 0.30)  # dot radius ≈ 79\nCLW = DR * 0.60  # connecting line width ≈ 47\n\n\ndef _r(x, y, w, h, fill, rx=0):\n    return (\n        f'<rect x=\"{x:.1f}\" y=\"{y:.1f}\" '\n        f'width=\"{max(w, 0.1):.1f}\" height=\"{max(h, 0.1):.1f}\" '\n        f'fill=\"{fill}\" rx=\"{rx:.1f}\"/>'\n    )\n\n\ndef _c(cx, cy, rad, fill=None, stroke=None, sw=2.5):\n    if fill:\n        return f'<circle cx=\"{cx:.1f}\" cy=\"{cy:.1f}\" r=\"{rad:.1f}\" fill=\"{fill}\"/>'\n    return f'<circle cx=\"{cx:.1f}\" cy=\"{cy:.1f}\" r=\"{rad:.1f}\" fill=\"none\" stroke=\"{stroke}\" stroke-width=\"{sw:.1f}\"/>'\n\n\ndef _t(x, y, txt, sz, fill, anchor=\"middle\", weight=\"normal\"):\n    return (\n        f'<text x=\"{x:.1f}\" y=\"{y:.1f}\" text-anchor=\"{anchor}\" '\n        f'dominant-baseline=\"middle\" font-size=\"{sz}\" fill=\"{fill}\" '\n        f'font-weight=\"{weight}\" font-family=\"Arial,Helvetica,sans-serif\">'\n        f\"{txt}</text>\"\n    )\n\n\ndef _hl(x1, x2, y, stroke, w=1.5):\n    return f'<line x1=\"{x1:.1f}\" y1=\"{y:.1f}\" x2=\"{x2:.1f}\" y2=\"{y:.1f}\" stroke=\"{stroke}\" stroke-width=\"{w:.1f}\"/>'\n\n\ndef _vl(x, y1, y2, stroke, w):\n    return f'<line x1=\"{x:.1f}\" y1=\"{y1:.1f}\" x2=\"{x:.1f}\" y2=\"{y2:.1f}\" stroke=\"{stroke}\" stroke-width=\"{w:.1f}\"/>'\n\n\nel = []\n\n# Background\nel.append(_r(0, 0, W, H, PAGE_BG))\n\n# Title and subtitle\nel.append(_t(W / 2, 72, \"upset-basic · pygal · anyplot.ai\", 34, INK, weight=\"bold\"))\nel.append(_t(W / 2, 130, \"Bug Report Overlap Across Software Modules\", 24, INK_SOFT))\n\n# Intersection bars (top panel, sorted descending by count)\nfor i, (combo, count) in enumerate(top_intersections):\n    cx = MX0 + (i + 0.5) * CW\n    bh = count / max_int * BMH\n    bw = CW * 0.55\n    bx = cx - bw / 2\n    by = AY - bh\n    deg = len(combo)\n    color = PALETTE[min(deg - 1, len(PALETTE) - 1)]\n    el.append(_r(bx, by, bw, bh, color, rx=5))\n    el.append(_t(cx, by - 24, str(count), 20, INK_SOFT))\n\n# Axis line under intersection bars\nel.append(_hl(MX0 - 10, W - 70, AY, INK_SOFT, 2))\n\n# Matrix row separators and set labels\nfor row_i in range(N_SETS):\n    ry = MT + (row_i + 0.5) * RH\n    if row_i > 0:\n        el.append(_hl(MX0 - 10, W - 70, MT + row_i * RH, INK_MUTED, 1))\n    el.append(_t(ML + LW, ry, set_names[row_i], 22, INK_SOFT, \"end\"))\n\n# Connecting lines (drawn before dots for correct z-order)\nfor col_i, (combo, _) in enumerate(top_intersections):\n    members = sorted(combo)\n    if len(members) > 1:\n        cx = MX0 + (col_i + 0.5) * CW\n        y1 = MT + (members[0] + 0.5) * RH\n        y2 = MT + (members[-1] + 0.5) * RH\n        el.append(_vl(cx, y1, y2, BRAND, CLW))\n\n# Dots (filled = member of set, empty circle = non-member)\nfor row_i in range(N_SETS):\n    ry = MT + (row_i + 0.5) * RH\n    for col_i, (combo, _) in enumerate(top_intersections):\n        cx = MX0 + (col_i + 0.5) * CW\n        if row_i in combo:\n            el.append(_c(cx, ry, DR, fill=BRAND))\n        else:\n            el.append(_c(cx, ry, DR * 0.70, stroke=INK_MUTED))\n\n# Set size horizontal bars (left panel)\nfor row_i, size in enumerate(set_sizes):\n    ry = MT + (row_i + 0.5) * RH\n    bh = RH * 0.40\n    bw = size / max_set * SBW\n    bx = ML + LW + G1 + SBW - bw\n    by = ry - bh / 2\n    el.append(_r(bx, by, bw, bh, BRAND, rx=3))\n    el.append(_t(bx - 10, ry, str(size), 18, INK_MUTED, \"end\"))\n\n# \"Set Size\" axis label below the set bars\nel.append(_t(ML + LW + G1 + SBW / 2, MB + 55, \"Set Size\", 22, INK_SOFT))\n\n# Degree color legend\nly = MB + 55\nlx0 = MX0 + 60\nel.append(_t(lx0, ly, \"Degree:\", 20, INK_SOFT, \"start\"))\nfor d in range(1, 6):\n    col = PALETTE[d - 1]\n    lx = lx0 + 200 + (d - 1) * 220\n    el.append(_r(lx - 35, ly - 24, 70, 48, col, rx=5))\n    el.append(_t(lx, ly, str(d), 20, INK, weight=\"bold\"))\n\n# Compose SVG\nsvg = (\n    f'<?xml version=\"1.0\" encoding=\"utf-8\"?>'\n    f'<svg xmlns=\"http://www.w3.org/2000/svg\" '\n    f'width=\"{W}\" height=\"{H}\" viewBox=\"0 0 {W} {H}\">' + \"\".join(el) + \"</svg>\"\n)\n\n# Save HTML (pygal produces interactive SVG-in-HTML)\nhtml = (\n    f\"<!DOCTYPE html><html><head><style>body{{margin:0;background:{PAGE_BG}}}</style></head><body>{svg}</body></html>\"\n)\nwith open(f\"plot-{THEME}.html\", \"w\", encoding=\"utf-8\") as f:\n    f.write(html)\n\n# Save PNG via cairosvg (pygal's rendering backend)\ncairosvg.svg2png(bytestring=svg.encode(), write_to=f\"plot-{THEME}.png\")\n"}