Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 65 additions & 64 deletions plots/venn-labeled-items/implementations/python/plotnine.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
""" anyplot.ai
venn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items
Library: plotnine 0.15.3 | Python 3.14.4
Quality: 85/100 | Created: 2026-04-25
Library: plotnine 0.15.7 | Python 3.13.14
Quality: 88/100 | Updated: 2026-06-25
"""

import os
import sys


# Avoid name collision so `from plotnine import ...` resolves to the package
_HERE = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if os.path.abspath(p) != _HERE]

Expand All @@ -19,6 +18,7 @@
coord_fixed,
element_blank,
element_rect,
geom_label,
geom_polygon,
geom_text,
ggplot,
Expand All @@ -33,119 +33,120 @@
# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Okabe-Ito categorical (positions 1, 2, 3)
COLOR_A = "#009E73" # brand green — ALWAYS first series
COLOR_B = "#C475FD" # vermillion
COLOR_C = "#4467A3" # blue
# Imprint palette positions 1–3
COLOR_A = "#009E73"
COLOR_B = "#C475FD"
COLOR_C = "#4467A3"

# Symmetric three-circle Venn geometry
RADIUS = 1.5
circle_meta = [
("Overhyped", -0.85, 0.50, COLOR_A),
("Actually Useful", 0.85, 0.50, COLOR_B),
("Secretly Loved", 0.00, -1.00, COLOR_C),
("Peak Instagram", -0.85, 0.50, COLOR_A),
("Actually Nutritious", 0.85, 0.50, COLOR_B),
("Surprisingly Addictive", 0.00, -1.00, COLOR_C),
]

# Polygon ring per circle (semi-transparent fills, colored outline)
theta = np.linspace(0, 2 * np.pi, 240)
circle_rows = []
for name, cx, cy, color in circle_meta:
for t in theta:
circle_rows.append({"name": name, "x": cx + RADIUS * np.cos(t), "y": cy + RADIUS * np.sin(t), "fill": color})
circles_df = pd.DataFrame(circle_rows)

# Items placed in their assigned zones
# Items placed in their assigned Venn zones
items_df = pd.DataFrame(
[
# A only — Overhyped
("NFTs", -2.05, 1.05),
("Metaverse", -2.10, 0.55),
("Smart Fridges", -2.00, 0.05),
# B only — Actually Useful
("Google Maps", 2.05, 1.05),
("Spreadsheets", 2.10, 0.55),
("Calendar Apps", 2.00, 0.05),
# C only — Secretly Loved
("Roller Skating", -0.70, -2.10),
("Soap Operas", 0.70, -2.10),
# A ∩ B
("ChatGPT", 0.00, 1.20),
("Smartwatches", 0.00, 0.85),
# A ∩ C
("Crocs", -1.10, -0.20),
("Vinyl Records", -1.05, -0.60),
# B ∩ C
("Dolly Parton", 1.10, -0.20),
("Spotify", 1.05, -0.60),
# A ∩ B ∩ C
("Sourdough", 0.00, 0.12),
("TikTok", 0.00, -0.32),
# A only — Peak Instagram (upper-left)
("Cloud Bread", -2.00, 1.30),
("Charcoal Ice Cream", -2.30, 0.52),
("Butterfly Pea Tea", -2.20, -0.05),
# B only — Actually Nutritious (upper-right)
("Sardines", 2.00, 1.30),
("Kimchi", 2.30, 0.52),
("Lentil Soup", 2.20, -0.05),
# C only — Surprisingly Addictive (bottom)
("Takis", -0.95, -2.25),
("Boba Tea", 0.00, -2.58),
("Funyuns", 0.95, -2.25),
# A ∩ B — photogenic and nutritious (top center)
("Avocado Toast", 0.00, 1.35),
("Overnight Oats", 0.00, 0.82),
# A ∩ C — photogenic and addictive (lower left, centered in zone)
("Cronuts", -0.90, -0.55),
("Dirty Soda", -0.90, -1.05),
# B ∩ C — nutritious and addictive (lower right, centered in zone)
("Greek Yogurt", 0.90, -0.55),
("Edamame", 0.90, -1.05),
# A ∩ B ∩ C (center)
("Sourdough", 0.00, 0.28),
("Matcha", 0.00, -0.22),
],
columns=["label", "x", "y"],
)

# Category labels rendered outside each circle, on its outer side
cat_left_df = pd.DataFrame({"label": ["Overhyped"], "x": [-1.95], "y": [2.30], "color": [COLOR_A]})
cat_right_df = pd.DataFrame({"label": ["Actually Useful"], "x": [1.95], "y": [2.30], "color": [COLOR_B]})
cat_bottom_df = pd.DataFrame({"label": ["Secretly Loved"], "x": [0.00], "y": [-2.85], "color": [COLOR_C]})
# Consolidated category labels — one DataFrame, one geom_text layer
cat_df = pd.DataFrame(
{
"label": ["Peak Instagram", "Actually Nutritious", "Surprisingly Addictive"],
"x": [-1.80, 1.80, 0.00],
"y": [2.32, 2.32, -2.85],
"color": [COLOR_A, COLOR_B, COLOR_C],
}
)

# Editorial-style title text inside the canvas
title_df = pd.DataFrame({"label": ["Chartgeist 2026"], "x": [0.0], "y": [3.25], "color": [INK]})
# Editorial title and spec subtitle
title_df = pd.DataFrame({"label": ["Food Trend Taxonomy"], "x": [0.0], "y": [3.22], "color": [INK]})
subtitle_df = pd.DataFrame(
{"label": ["venn-labeled-items · plotnine · anyplot.ai"], "x": [0.0], "y": [2.85], "color": [INK_MUTED]}
{"label": ["venn-labeled-items · python · plotnine · anyplot.ai"], "x": [0.0], "y": [2.82], "color": [INK_MUTED]}
)

# Plot
plot = (
ggplot()
+ geom_polygon(
data=circles_df, mapping=aes(x="x", y="y", group="name", fill="fill", color="fill"), alpha=0.22, size=1.4
data=circles_df, mapping=aes(x="x", y="y", group="name", fill="fill"), color=INK_SOFT, alpha=0.22, size=0.6
)
+ geom_text(data=items_df, mapping=aes(x="x", y="y", label="label"), size=13, color=INK, family="serif")
+ geom_text(
data=cat_left_df,
mapping=aes(x="x", y="y", label="label", color="color"),
size=22,
fontweight="bold",
# geom_label gives each item a clean background box — more readable in overlapping zones
+ geom_label(
data=items_df,
mapping=aes(x="x", y="y", label="label"),
size=11,
color=INK,
fill=ELEVATED_BG,
label_size=0,
label_padding=0.12,
family="serif",
ha="center",
)
+ geom_text(
data=cat_right_df,
mapping=aes(x="x", y="y", label="label", color="color"),
size=22,
fontweight="bold",
family="serif",
ha="center",
)
# Consolidated category label layer (was three separate geom_text calls)
+ geom_text(
data=cat_bottom_df,
data=cat_df,
mapping=aes(x="x", y="y", label="label", color="color"),
size=22,
size=18,
fontweight="bold",
family="serif",
ha="center",
)
+ geom_text(
data=title_df,
mapping=aes(x="x", y="y", label="label", color="color"),
size=30,
size=26,
fontweight="bold",
fontstyle="italic",
family="serif",
)
+ geom_text(data=subtitle_df, mapping=aes(x="x", y="y", label="label", color="color"), size=12, family="serif")
+ geom_text(data=subtitle_df, mapping=aes(x="x", y="y", label="label", color="color"), size=14, family="serif")
+ scale_fill_identity()
+ scale_color_identity()
+ scale_x_continuous(limits=(-3.5, 3.5), expand=(0, 0))
+ scale_y_continuous(limits=(-3.5, 3.5), expand=(0, 0))
+ coord_fixed(ratio=1)
+ theme(
figure_size=(12, 12),
figure_size=(6, 6),
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
axis_title=element_blank(),
Expand All @@ -158,4 +159,4 @@
)
)

plot.save(f"plot-{THEME}.png", dpi=300, verbose=False)
plot.save(f"plot-{THEME}.png", dpi=400, width=6, height=6, units="in", verbose=False)
Loading
Loading