{"spec_id":"pictogram-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\npictogram-basic: Pictogram Chart (Isotype Visualization)\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nfrom lets_plot import *\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\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Subtle alternating lane color derived from theme surface\nLANE_BG = \"#E4E2DB\" if THEME == \"light\" else \"#2A2A26\"\n\n# Imprint palette — canonical order, first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data — Top coffee-producing countries (thousands of metric tonnes, ~2023)\ncategories = [\"Brazil\", \"Vietnam\", \"Colombia\", \"Indonesia\", \"Ethiopia\"]\nvalues = [45, 32, 14, 11, 8]\nicon_value = 5  # Each icon represents 5 thousand metric tonnes\n\nmax_icons = max(v // icon_value + (1 if v % icon_value else 0) for v in values)\n\nMIN_ALPHA = 0.33  # floor so faintest partial icon remains clearly visible\n\n# Build pictogram grid — full icons at alpha=1.0, partial via fractional alpha\ntile_data = {\"category\": [], \"col\": [], \"row\": [], \"alpha\": [], \"value\": []}\n\nfor i, (cat, val) in enumerate(zip(categories, values)):\n    y_pos = len(categories) - 1 - i  # Highest value at top\n    full_icons = val // icon_value\n    remainder = val % icon_value\n    for c in range(full_icons):\n        tile_data[\"category\"].append(cat)\n        tile_data[\"col\"].append(float(c))\n        tile_data[\"row\"].append(float(y_pos))\n        tile_data[\"alpha\"].append(1.0)\n        tile_data[\"value\"].append(val)\n    if remainder > 0:\n        tile_data[\"category\"].append(cat)\n        tile_data[\"col\"].append(float(full_icons))\n        tile_data[\"row\"].append(float(y_pos))\n        tile_data[\"alpha\"].append(max(MIN_ALPHA, remainder / icon_value))\n        tile_data[\"value\"].append(val)\n\n# Alternating background lanes for readability (even category indices)\neven_lanes = {\n    \"ymin\": [float(len(categories) - 1 - i) - 0.48 for i in range(len(categories)) if i % 2 == 0],\n    \"ymax\": [float(len(categories) - 1 - i) + 0.48 for i in range(len(categories)) if i % 2 == 0],\n}\n\n# Value labels placed to the right of each row\nlabel_data = {\n    \"col\": [float(max_icons) + 0.3] * len(categories),\n    \"row\": [float(len(categories) - 1 - i) for i in range(len(categories))],\n    \"label\": [f\"{v}k MT\" for v in values],\n}\n\n# Top producer annotation for data storytelling\nanno_data = {\"col\": [float(max_icons) + 0.3], \"row\": [float(len(categories) - 1) + 0.42], \"label\": [\"★ Top producer\"]}\n\n# Icon scale legend note in bottom-left corner for self-contained chart\nscale_note = {\"col\": [-0.45], \"row\": [-0.44], \"label\": [\"● = 5k MT\"]}\n\ny_breaks = [float(len(categories) - 1 - i) for i in range(len(categories))]\n\ntitle = \"pictogram-basic · python · letsplot · anyplot.ai\"\nsubtitle = \"Coffee Production by Country — Each icon represents 5 thousand metric tonnes\"\n\n# Plot\nplot = (\n    ggplot()\n    + geom_rect(\n        aes(ymin=\"ymin\", ymax=\"ymax\"), data=even_lanes, xmin=-0.6, xmax=float(max_icons) + 2.2, fill=LANE_BG, size=0\n    )\n    + geom_point(\n        aes(x=\"col\", y=\"row\", alpha=\"alpha\", fill=\"category\"),\n        data=tile_data,\n        shape=21,\n        size=12,\n        color=PAGE_BG,\n        stroke=1.5,\n        tooltips=layer_tooltips().line(\"@category\").line(\"Total: @value thousand metric tonnes\").format(\"@value\", \"d\"),\n    )\n    + scale_alpha_identity()\n    + scale_fill_manual(values=IMPRINT_PALETTE, limits=categories)\n    + geom_text(aes(x=\"col\", y=\"row\", label=\"label\"), data=label_data, size=5, color=INK_SOFT, hjust=0, fontface=\"bold\")\n    + geom_text(\n        aes(x=\"col\", y=\"row\", label=\"label\"),\n        data=anno_data,\n        size=4,\n        color=IMPRINT_PALETTE[0],\n        hjust=0,\n        fontface=\"italic\",\n    )\n    + geom_text(\n        aes(x=\"col\", y=\"row\", label=\"label\"), data=scale_note, size=3.5, color=INK_SOFT, hjust=0, fontface=\"italic\"\n    )\n    + scale_y_continuous(breaks=y_breaks, labels=categories, limits=[-0.6, len(categories) - 0.3], expand=[0, 0])\n    + scale_x_continuous(limits=[-0.6, float(max_icons) + 2.3], expand=[0, 0])\n    + labs(x=\"\", y=\"\", title=title, subtitle=subtitle)\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        plot_subtitle=element_text(size=11, color=INK_SOFT),\n        axis_title=element_blank(),\n        axis_text_y=element_text(size=10, face=\"bold\", color=INK),\n        axis_text_x=element_blank(),\n        axis_ticks=element_blank(),\n        panel_grid=element_blank(),\n        legend_position=\"none\",\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG, size=0),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG, size=0),\n    )\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}