{"spec_id":"funnel-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nfunnel-basic: Basic Funnel Chart\nLibrary: plotnine 0.15.3 | Python 3.14.4\nQuality: 87/100 | Updated: 2026-04-26\n\"\"\"\n\n# Script filename \"plotnine.py\" shadows the plotnine package on sys.path.\n# Drop the script's own directory before importing.\nimport os\nimport sys\n\n\nsys.path = [p for p in sys.path if not p.endswith(\"implementations/python\")]\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_polygon,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_identity,\n    scale_fill_manual,\n    scale_y_reverse,\n    theme,\n    theme_void,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\n\n# imprint palette — first stage is brand green (#009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n# Slot 1 (lavender) is the lightest hue and needs dark text; the others are\n# saturated/dark enough for white.\nTEXT_ON_FILL = [\"white\", INK, \"white\", \"white\", \"white\"]\n\n# Data — sales funnel example from specification\nstages = [\"Awareness\", \"Interest\", \"Consideration\", \"Intent\", \"Purchase\"]\nvalues = [1000, 600, 400, 200, 100]\nn_stages = len(stages)\nmax_value = values[0]\n\n# Build trapezoid polygon vertices for each stage\ngap = 0.06\npolygons = []\nlabels = []\nfor i, (stage, value) in enumerate(zip(stages, values, strict=True)):\n    w_top = value / max_value\n    # Flat bottom on the last segment — match its own width.\n    w_bot = (values[i + 1] / max_value) if i + 1 < n_stages else w_top\n    y_top = i + gap / 2\n    y_bot = (i + 1) - gap / 2\n    polygons.extend(\n        [\n            {\"stage\": stage, \"x\": -w_top / 2, \"y\": y_top, \"order\": 0},\n            {\"stage\": stage, \"x\": w_top / 2, \"y\": y_top, \"order\": 1},\n            {\"stage\": stage, \"x\": w_bot / 2, \"y\": y_bot, \"order\": 2},\n            {\"stage\": stage, \"x\": -w_bot / 2, \"y\": y_bot, \"order\": 3},\n        ]\n    )\n    pct = value / max_value * 100\n    labels.append(\n        {\n            \"stage\": stage,\n            \"x\": 0,\n            \"y\": (y_top + y_bot) / 2,\n            \"label\": f\"{stage}\\n{value:,}  ·  {pct:.0f}%\",\n            \"text_color\": TEXT_ON_FILL[i],\n        }\n    )\n\ndf_poly = pd.DataFrame(polygons)\ndf_poly[\"stage\"] = pd.Categorical(df_poly[\"stage\"], categories=stages, ordered=True)\ndf_labels = pd.DataFrame(labels)\n\n# Plot\nplot = (\n    ggplot(df_poly, aes(x=\"x\", y=\"y\", fill=\"stage\", group=\"stage\"))\n    + geom_polygon(color=PAGE_BG, size=2)\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\", color=\"text_color\"),\n        data=df_labels,\n        size=14,\n        fontweight=\"bold\",\n        inherit_aes=False,\n    )\n    + scale_fill_manual(values=IMPRINT[:n_stages], guide=None)\n    + scale_color_identity()\n    + scale_y_reverse()\n    + labs(title=\"funnel-basic · plotnine · anyplot.ai\", x=\"\", y=\"\")\n    + theme_void()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=24, color=INK, weight=\"medium\", ha=\"center\"),\n        plot_margin=0.02,\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        panel_grid=element_blank(),\n        legend_position=\"none\",\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}