{"spec_id":"venn-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nvenn-basic: Venn Diagram\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-11\n\"\"\"\n\nimport math\nimport os\nimport site\nimport sys\n\n\n# Ensure site-packages comes before current directory to avoid shadowing\nsite_packages = site.getsitepackages()[0]\nsys.path.insert(0, site_packages)\n\nimport altair as alt\nimport pandas as pd\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\"\n\n# Okabe-Ito palette (first series always #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Three overlapping research disciplines\nset_labels = [\"Machine Learning\", \"Statistics\", \"Data Engineering\"]\nset_sizes = [100, 80, 60]\n# Overlaps: AB=30, AC=20, BC=25, ABC=10\nonly_a = 100 - 30 - 20 + 10  # 60\nonly_b = 80 - 30 - 25 + 10  # 35\nonly_c = 60 - 20 - 25 + 10  # 25\nonly_ab = 30 - 10  # 20\nonly_ac = 20 - 10  # 10\nonly_bc = 25 - 10  # 15\nabc = 10\n\n# Calculate proportional radii based on set sizes\nbase_radius = 280\nradius_a = base_radius * math.sqrt(set_sizes[0] / 100)\nradius_b = base_radius * math.sqrt(set_sizes[1] / 100)\nradius_c = base_radius * math.sqrt(set_sizes[2] / 100)\n\n# Position circles in triangular arrangement, centered vertically on canvas\ncenter_x, center_y = 800, 450\ncircle_spacing = 180\nangle_a = math.radians(210)\nangle_b = math.radians(330)\nangle_c = math.radians(90)\n\ncx_a = center_x + circle_spacing * math.cos(angle_a)\ncy_a = center_y - circle_spacing * math.sin(angle_a)\ncx_b = center_x + circle_spacing * math.cos(angle_b)\ncy_b = center_y - circle_spacing * math.sin(angle_b)\ncx_c = center_x + circle_spacing * math.cos(angle_c)\ncy_c = center_y - circle_spacing * math.sin(angle_c)\n\n# Circle sizes for mark_point\ncircle_size_a = radius_a * radius_a * 3.14\ncircle_size_b = radius_b * radius_b * 3.14\ncircle_size_c = radius_c * radius_c * 3.14\n\n# Circle centers data with Okabe-Ito colors\nfill_centers = pd.DataFrame(\n    {\n        \"x\": [cx_a, cx_b, cx_c],\n        \"y\": [cy_a, cy_b, cy_c],\n        \"color\": IMPRINT[0:3],\n        \"set\": set_labels,\n        \"size\": [circle_size_a, circle_size_b, circle_size_c],\n    }\n)\n\n# Region counts positioned relative to circle centers\nonly_offset = 0.5\nregion_data = pd.DataFrame(\n    {\n        \"x\": [\n            cx_a + (cx_a - center_x) * only_offset,\n            cx_b + (cx_b - center_x) * only_offset,\n            cx_c,\n            (cx_a + cx_b) / 2,\n            (cx_a + cx_c) / 2,\n            (cx_b + cx_c) / 2,\n            center_x,\n        ],\n        \"y\": [\n            cy_a + (cy_a - center_y) * only_offset,\n            cy_b + (cy_b - center_y) * only_offset,\n            cy_c + radius_c * 0.6,\n            (cy_a + cy_b) / 2 - 50,\n            (cy_a + cy_c) / 2 + 20,\n            (cy_b + cy_c) / 2 + 20,\n            center_y,\n        ],\n        \"count\": [str(only_a), str(only_b), str(only_c), str(only_ab), str(only_ac), str(only_bc), str(abc)],\n    }\n)\n\n# Set name labels positioned outside circles\nlabel_offset = 1.3\nset_label_data = pd.DataFrame(\n    {\n        \"x\": [cx_a + (cx_a - center_x) * label_offset, cx_b + (cx_b - center_x) * label_offset, cx_c],\n        \"y\": [cy_a + (cy_a - center_y) * label_offset, cy_b + (cy_b - center_y) * label_offset, cy_c + radius_c + 60],\n        \"label\": set_labels,\n    }\n)\n\n# Draw filled circles with transparency\nbackground_circles = (\n    alt.Chart(fill_centers)\n    .mark_point(shape=\"circle\", filled=True, opacity=0.3)\n    .encode(\n        x=alt.X(\"x:Q\", scale=alt.Scale(domain=[200, 1400]), axis=None),\n        y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[100, 800]), axis=None),\n        color=alt.Color(\"color:N\", scale=None, legend=None),\n        size=alt.Size(\"size:Q\", scale=None, legend=None),\n    )\n)\n\n# Circle outlines\noutline_circles = (\n    alt.Chart(fill_centers)\n    .mark_point(shape=\"circle\", filled=False, strokeWidth=5)\n    .encode(\n        x=alt.X(\"x:Q\", scale=alt.Scale(domain=[200, 1400]), axis=None),\n        y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[100, 800]), axis=None),\n        stroke=alt.Color(\"color:N\", scale=None, legend=None),\n        size=alt.Size(\"size:Q\", scale=None, legend=None),\n    )\n)\n\n# Region count labels\ncounts_layer = (\n    alt.Chart(region_data)\n    .mark_text(fontSize=32, fontWeight=\"bold\", color=INK)\n    .encode(\n        x=alt.X(\"x:Q\", scale=alt.Scale(domain=[200, 1400]), axis=None),\n        y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[100, 800]), axis=None),\n        text=\"count:N\",\n    )\n)\n\n# Set name labels\nnames_layer = (\n    alt.Chart(set_label_data)\n    .mark_text(fontSize=28, fontWeight=\"bold\", color=INK_SOFT)\n    .encode(\n        x=alt.X(\"x:Q\", scale=alt.Scale(domain=[200, 1400]), axis=None),\n        y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[100, 800]), axis=None),\n        text=\"label:N\",\n    )\n)\n\n# Combine all layers with theme-adaptive styling\nchart = (\n    alt.layer(background_circles, outline_circles, counts_layer, names_layer)\n    .properties(\n        width=1600,\n        height=900,\n        background=PAGE_BG,\n        title=alt.Title(text=\"venn-basic · altair · anyplot.ai\", fontSize=28, anchor=\"middle\", color=INK),\n    )\n    .configure_view(fill=PAGE_BG, stroke=None, strokeWidth=0)\n)\n\n# Save outputs\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}