|
| 1 | +""" anyplot.ai |
| 2 | +venn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items |
| 3 | +Library: bokeh 3.9.0 | Python 3.14.4 |
| 4 | +Quality: 86/100 | Created: 2026-04-25 |
| 5 | +""" |
| 6 | + |
| 7 | +import os |
| 8 | + |
| 9 | +import numpy as np |
| 10 | +from bokeh.io import export_png, output_file, save |
| 11 | +from bokeh.models import Label |
| 12 | +from bokeh.plotting import figure |
| 13 | + |
| 14 | + |
| 15 | +# Theme tokens |
| 16 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 17 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 18 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 19 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 20 | + |
| 21 | +# Okabe-Ito palette — first circle is brand green, then vermillion, then blue |
| 22 | +COLOR_A = "#009E73" # Overhyped |
| 23 | +COLOR_B = "#D55E00" # Actually Useful |
| 24 | +COLOR_C = "#0072B2" # Secretly Loved |
| 25 | + |
| 26 | +# Geometry — equilateral triangle of centers (apex pointing down) |
| 27 | +r = 1.0 |
| 28 | +s = 1.1 |
| 29 | +h_low = s * np.sqrt(3) / 6 |
| 30 | +center_a = (-s / 2, h_low) |
| 31 | +center_b = (s / 2, h_low) |
| 32 | +center_c = (0.0, -2 * h_low) |
| 33 | + |
| 34 | +# Data — circle categories with editorial labels positioned outside each circle |
| 35 | +circles = [ |
| 36 | + {"name": "Overhyped", "color": COLOR_A, "center": center_a, "label_xy": (-1.65, 1.45), "align": "right"}, |
| 37 | + {"name": "Actually Useful", "color": COLOR_B, "center": center_b, "label_xy": (1.65, 1.45), "align": "left"}, |
| 38 | + {"name": "Secretly Loved", "color": COLOR_C, "center": center_c, "label_xy": (0.0, -1.92), "align": "center"}, |
| 39 | +] |
| 40 | + |
| 41 | +# Items distributed across the seven interior zones |
| 42 | +items = [ |
| 43 | + # A only — Overhyped, neither useful nor loved |
| 44 | + {"label": "NFTs", "x": -1.42, "y": 0.88}, |
| 45 | + {"label": "Metaverse", "x": -1.50, "y": 0.32}, |
| 46 | + {"label": "Web3", "x": -1.30, "y": -0.05}, |
| 47 | + # B only — Useful, neither overhyped nor loved |
| 48 | + {"label": "Google Maps", "x": 1.42, "y": 0.88}, |
| 49 | + {"label": "Sticky Notes", "x": 1.40, "y": 0.30}, |
| 50 | + # C only — Loved, neither overhyped nor useful |
| 51 | + {"label": "Karaoke", "x": -0.45, "y": -1.30}, |
| 52 | + {"label": "Postcards", "x": 0.45, "y": -1.30}, |
| 53 | + # AB — Overhyped + Useful |
| 54 | + {"label": "Smartphones", "x": 0.00, "y": 0.92}, |
| 55 | + {"label": "Email", "x": 0.00, "y": 0.62}, |
| 56 | + # AC — Overhyped + Loved |
| 57 | + {"label": "Crocs", "x": -0.78, "y": -0.18}, |
| 58 | + {"label": "Pumpkin Spice", "x": -0.55, "y": -0.50}, |
| 59 | + # BC — Useful + Loved |
| 60 | + {"label": "Spotify", "x": 0.78, "y": -0.18}, |
| 61 | + {"label": "Dolly Parton", "x": 0.55, "y": -0.50}, |
| 62 | + # ABC — all three |
| 63 | + {"label": "Sourdough", "x": 0.00, "y": 0.10}, |
| 64 | + {"label": "TikTok", "x": 0.00, "y": -0.20}, |
| 65 | +] |
| 66 | + |
| 67 | +# Plot — square canvas suits the radial Venn layout |
| 68 | +p = figure( |
| 69 | + width=3600, |
| 70 | + height=3600, |
| 71 | + title="Tech Vibes 2026 · venn-labeled-items · bokeh · anyplot.ai", |
| 72 | + x_range=(-2.7, 2.7), |
| 73 | + y_range=(-2.7, 2.7), |
| 74 | + toolbar_location=None, |
| 75 | + tools="", |
| 76 | + match_aspect=True, |
| 77 | +) |
| 78 | + |
| 79 | +# Three semi-transparent circles |
| 80 | +for circle in circles: |
| 81 | + cx, cy = circle["center"] |
| 82 | + p.ellipse( |
| 83 | + x=cx, |
| 84 | + y=cy, |
| 85 | + width=2 * r, |
| 86 | + height=2 * r, |
| 87 | + fill_color=circle["color"], |
| 88 | + fill_alpha=0.22, |
| 89 | + line_color=circle["color"], |
| 90 | + line_width=4, |
| 91 | + line_alpha=0.85, |
| 92 | + ) |
| 93 | + |
| 94 | +# Category names outside each circle, in the circle's own color |
| 95 | +for circle in circles: |
| 96 | + lx, ly = circle["label_xy"] |
| 97 | + p.add_layout( |
| 98 | + Label( |
| 99 | + x=lx, |
| 100 | + y=ly, |
| 101 | + text=circle["name"], |
| 102 | + text_font="serif", |
| 103 | + text_font_size="64pt", |
| 104 | + text_font_style="italic", |
| 105 | + text_color=circle["color"], |
| 106 | + text_align=circle["align"], |
| 107 | + text_baseline="middle", |
| 108 | + ) |
| 109 | + ) |
| 110 | + |
| 111 | +# Items as text-only placements inside their assigned zones |
| 112 | +for item in items: |
| 113 | + p.add_layout( |
| 114 | + Label( |
| 115 | + x=item["x"], |
| 116 | + y=item["y"], |
| 117 | + text=item["label"], |
| 118 | + text_font="serif", |
| 119 | + text_font_size="38pt", |
| 120 | + text_color=INK, |
| 121 | + text_align="center", |
| 122 | + text_baseline="middle", |
| 123 | + ) |
| 124 | + ) |
| 125 | + |
| 126 | +# Editorial subtitle anchored under the diagram |
| 127 | +p.add_layout( |
| 128 | + Label( |
| 129 | + x=0.0, |
| 130 | + y=-2.35, |
| 131 | + text="A field guide to fifteen things, three feelings, and seven overlapping truths", |
| 132 | + text_font="serif", |
| 133 | + text_font_size="30pt", |
| 134 | + text_font_style="italic", |
| 135 | + text_color=INK_SOFT, |
| 136 | + text_align="center", |
| 137 | + text_baseline="middle", |
| 138 | + ) |
| 139 | +) |
| 140 | + |
| 141 | +# Style — gridless editorial chrome |
| 142 | +p.background_fill_color = PAGE_BG |
| 143 | +p.border_fill_color = PAGE_BG |
| 144 | +p.outline_line_color = None |
| 145 | + |
| 146 | +p.title.text_font = "serif" |
| 147 | +p.title.text_font_size = "54pt" |
| 148 | +p.title.text_font_style = "normal" |
| 149 | +p.title.text_color = INK |
| 150 | +p.title.align = "center" |
| 151 | + |
| 152 | +p.axis.visible = False |
| 153 | +p.grid.visible = False |
| 154 | + |
| 155 | +# Save |
| 156 | +export_png(p, filename=f"plot-{THEME}.png") |
| 157 | +output_file(f"plot-{THEME}.html") |
| 158 | +save(p) |
0 commit comments