|
| 1 | +""" anyplot.ai |
| 2 | +venn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items |
| 3 | +Library: seaborn 0.13.2 | Python 3.14.4 |
| 4 | +Quality: 88/100 | Created: 2026-04-25 |
| 5 | +""" |
| 6 | + |
| 7 | +import os |
| 8 | + |
| 9 | +import matplotlib.pyplot as plt |
| 10 | +import pandas as pd |
| 11 | +import seaborn as sns |
| 12 | +from matplotlib.patches import Circle |
| 13 | + |
| 14 | + |
| 15 | +# Theme tokens |
| 16 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 17 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 18 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 19 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 20 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 21 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 22 | + |
| 23 | +OKABE_GREEN = "#009E73" |
| 24 | +OKABE_ORANGE = "#D55E00" |
| 25 | +OKABE_BLUE = "#0072B2" |
| 26 | + |
| 27 | +sns.set_theme( |
| 28 | + style="white", |
| 29 | + rc={ |
| 30 | + "figure.facecolor": PAGE_BG, |
| 31 | + "axes.facecolor": PAGE_BG, |
| 32 | + "axes.edgecolor": PAGE_BG, |
| 33 | + "axes.labelcolor": INK, |
| 34 | + "text.color": INK, |
| 35 | + "font.family": "serif", |
| 36 | + }, |
| 37 | +) |
| 38 | + |
| 39 | +# Data |
| 40 | +circles = [ |
| 41 | + {"name": "Overhyped", "color": OKABE_GREEN, "center": (0.00, 0.55)}, |
| 42 | + {"name": "Actually Useful", "color": OKABE_ORANGE, "center": (-0.476, -0.275)}, |
| 43 | + {"name": "Secretly Loved", "color": OKABE_BLUE, "center": (0.476, -0.275)}, |
| 44 | +] |
| 45 | + |
| 46 | +zone_items = { |
| 47 | + "A": ["NFTs", "Metaverse", "Web3"], |
| 48 | + "B": ["Spreadsheets", "Calculators"], |
| 49 | + "C": ["Karaoke", "Bob Ross"], |
| 50 | + "AB": ["Crypto", "ChatGPT"], |
| 51 | + "AC": ["TikTok", "Pumpkin Spice"], |
| 52 | + "BC": ["Google Maps", "Dolly Parton", "IKEA Meatballs"], |
| 53 | + "ABC": ["Sourdough"], |
| 54 | + "outside": ["Jury Duty"], |
| 55 | +} |
| 56 | + |
| 57 | +zone_centroids = { |
| 58 | + "A": (0.00, 1.05), |
| 59 | + "B": (-1.00, -0.62), |
| 60 | + "C": (1.00, -0.62), |
| 61 | + "AB": (-0.55, 0.32), |
| 62 | + "AC": (0.55, 0.32), |
| 63 | + "BC": (0.00, -0.65), |
| 64 | + "ABC": (0.00, 0.05), |
| 65 | + "outside": (-1.95, 1.30), |
| 66 | +} |
| 67 | + |
| 68 | +# Build a long-form items dataframe so we can use seaborn for the marker layer. |
| 69 | +rows = [] |
| 70 | +spacing = 0.20 |
| 71 | +for zone, labels in zone_items.items(): |
| 72 | + cx, cy = zone_centroids[zone] |
| 73 | + n = len(labels) |
| 74 | + start_y = cy + (n - 1) * spacing / 2 |
| 75 | + for i, label in enumerate(labels): |
| 76 | + rows.append( |
| 77 | + { |
| 78 | + "zone": zone, |
| 79 | + "label": label, |
| 80 | + "x": cx, |
| 81 | + "y": start_y - i * spacing, |
| 82 | + "is_outside": zone == "outside", |
| 83 | + "is_triple": zone == "ABC", |
| 84 | + } |
| 85 | + ) |
| 86 | +items_df = pd.DataFrame(rows) |
| 87 | + |
| 88 | +# Plot — square canvas suits the symmetric Venn layout |
| 89 | +fig, ax = plt.subplots(figsize=(12, 12), facecolor=PAGE_BG) |
| 90 | +ax.set_facecolor(PAGE_BG) |
| 91 | +ax.set_aspect("equal") |
| 92 | + |
| 93 | +r = 0.85 |
| 94 | +for c in circles: |
| 95 | + ax.add_patch(Circle(c["center"], radius=r, facecolor=c["color"], alpha=0.20, edgecolor=c["color"], linewidth=2.0)) |
| 96 | + |
| 97 | +# Subtle focal-point highlight on the triple-overlap zone for editorial emphasis |
| 98 | +ax.add_patch( |
| 99 | + Circle( |
| 100 | + zone_centroids["ABC"], |
| 101 | + radius=0.13, |
| 102 | + facecolor=INK, |
| 103 | + alpha=0.05, |
| 104 | + edgecolor=INK_SOFT, |
| 105 | + linewidth=0.8, |
| 106 | + linestyle=(0, (2, 2)), |
| 107 | + ) |
| 108 | +) |
| 109 | + |
| 110 | +# Seaborn scatter layer: small dot markers anchor every item. |
| 111 | +# Different palette + size for the focal triple-overlap zone (DE-03 storytelling). |
| 112 | +regular = items_df[~items_df["is_triple"]] |
| 113 | +triple = items_df[items_df["is_triple"]] |
| 114 | +sns.scatterplot(data=regular, x="x", y="y", color=INK_SOFT, s=18, alpha=0.55, edgecolor="none", legend=False, ax=ax) |
| 115 | +sns.scatterplot( |
| 116 | + data=triple, x="x", y="y", color=INK, s=44, alpha=0.85, edgecolor=PAGE_BG, linewidth=1.2, legend=False, ax=ax |
| 117 | +) |
| 118 | + |
| 119 | +# Category labels (outside each circle, on the outer side) |
| 120 | +ax.text( |
| 121 | + 0, |
| 122 | + 0.55 + r + 0.06, |
| 123 | + circles[0]["name"], |
| 124 | + ha="center", |
| 125 | + va="bottom", |
| 126 | + fontsize=24, |
| 127 | + fontweight="bold", |
| 128 | + color=circles[0]["color"], |
| 129 | + clip_on=False, |
| 130 | +) |
| 131 | +ax.text( |
| 132 | + -1.40, |
| 133 | + -0.275 - 0.80, |
| 134 | + circles[1]["name"], |
| 135 | + ha="right", |
| 136 | + va="top", |
| 137 | + fontsize=24, |
| 138 | + fontweight="bold", |
| 139 | + color=circles[1]["color"], |
| 140 | + clip_on=False, |
| 141 | +) |
| 142 | +ax.text( |
| 143 | + 1.40, |
| 144 | + -0.275 - 0.80, |
| 145 | + circles[2]["name"], |
| 146 | + ha="left", |
| 147 | + va="top", |
| 148 | + fontsize=24, |
| 149 | + fontweight="bold", |
| 150 | + color=circles[2]["color"], |
| 151 | + clip_on=False, |
| 152 | +) |
| 153 | + |
| 154 | +# Item text labels — slightly above their dot markers |
| 155 | +label_offset = 0.055 |
| 156 | +for _, row in items_df.iterrows(): |
| 157 | + is_triple = row["is_triple"] |
| 158 | + is_outside = row["is_outside"] |
| 159 | + ax.text( |
| 160 | + row["x"], |
| 161 | + row["y"] + label_offset, |
| 162 | + row["label"], |
| 163 | + ha="center", |
| 164 | + va="bottom", |
| 165 | + fontsize=20 if is_triple else 17, |
| 166 | + fontweight="bold" if is_triple else "normal", |
| 167 | + color=INK_MUTED if is_outside else INK, |
| 168 | + style="italic" if is_outside else "normal", |
| 169 | + clip_on=False, |
| 170 | + ) |
| 171 | + |
| 172 | +# Hint label for the "outside" stack |
| 173 | +ax.text( |
| 174 | + -1.95, 1.55, "(outside all)", ha="center", va="bottom", fontsize=14, color=INK_MUTED, style="italic", clip_on=False |
| 175 | +) |
| 176 | + |
| 177 | +# Title (mandatory format) + editorial subtitle |
| 178 | +fig.suptitle("venn-labeled-items · seaborn · anyplot.ai", fontsize=26, fontweight="medium", color=INK, y=0.965) |
| 179 | +fig.text( |
| 180 | + 0.5, |
| 181 | + 0.918, |
| 182 | + "Tech & Trends — a Chartgeist taxonomy of what we love, use, and overrate", |
| 183 | + ha="center", |
| 184 | + va="top", |
| 185 | + fontsize=18, |
| 186 | + color=INK_SOFT, |
| 187 | + style="italic", |
| 188 | +) |
| 189 | + |
| 190 | +ax.set_xlim(-2.35, 2.35) |
| 191 | +ax.set_ylim(-2.05, 1.85) |
| 192 | +ax.axis("off") |
| 193 | + |
| 194 | +plt.savefig(f"plot-{THEME}.png", dpi=200, bbox_inches="tight", facecolor=PAGE_BG) |
0 commit comments