{"spec_id":"funnel-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nfunnel-basic: Basic Funnel Chart\nLibrary: letsplot 4.9.0 | Python 3.14.4\nQuality: 86/100 | Updated: 2026-04-26\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_polygon,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    scale_color_identity,\n    scale_fill_manual,\n    scale_y_reverse,\n    theme,\n    theme_void,\n)\n\n\nLetsPlot.setup_html()\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# Okabe-Ito palette — first stage is brand green (#009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n# Light orange (#AE3030) needs dark text for contrast; others use white.\nTEXT_ON_FILL = [\"white\", \"white\", \"white\", \"white\", INK]\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    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()\n    + geom_polygon(aes(x=\"x\", y=\"y\", fill=\"stage\", group=\"stage\"), data=df_poly, color=PAGE_BG, size=2)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\", color=\"text_color\"), data=df_labels, size=12, fontface=\"bold\")\n    + scale_fill_manual(values=IMPRINT[:n_stages], guide=\"none\")\n    + scale_color_identity()\n    + scale_y_reverse()\n    + labs(title=\"funnel-basic · letsplot · anyplot.ai\", x=\"\", y=\"\")\n    + theme_void()\n    + theme(\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, face=\"bold\", hjust=0.5),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        panel_grid=element_blank(),\n        legend_position=\"none\",\n    )\n    + ggsize(1600, 900)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}