{"spec_id":"marimekko-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nmarimekko-basic: Basic Marimekko Chart\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 94/100 | Updated: 2026-07-24\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import: this file is named bokeh.py, which shadows the installed\n# bokeh package when its directory sits at the front of sys.path.\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _this_dir]\n\nimport time\nfrom pathlib import Path\n\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, LabelSet\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette (positions 1-4)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: Market share across regions with varying market sizes\nregions = [\"North America\", \"Europe\", \"Asia Pacific\", \"Latin America\"]\nproducts = [\"Electronics\", \"Apparel\", \"Home & Garden\", \"Food & Beverage\"]\n\nmarket_data = {\n    \"North America\": {\"Electronics\": 120, \"Apparel\": 80, \"Home & Garden\": 60, \"Food & Beverage\": 140},\n    \"Europe\": {\"Electronics\": 90, \"Apparel\": 110, \"Home & Garden\": 50, \"Food & Beverage\": 100},\n    \"Asia Pacific\": {\"Electronics\": 200, \"Apparel\": 150, \"Home & Garden\": 80, \"Food & Beverage\": 170},\n    \"Latin America\": {\"Electronics\": 40, \"Apparel\": 35, \"Home & Garden\": 25, \"Food & Beverage\": 50},\n}\n\n# Calculate totals for each region (determines bar width)\nregion_totals = {region: sum(market_data[region].values()) for region in regions}\ntotal_all = sum(region_totals.values())\nleading_region = max(regions, key=lambda r: region_totals[r])\n\n# Calculate normalized widths (proportional to region total)\nbar_gap = 0.02\ntotal_width = 1.0 - (len(regions) - 1) * bar_gap\nwidths = {region: (region_totals[region] / total_all) * total_width for region in regions}\n\n# Build rectangle data for each segment (bottom-up stacking within each bar)\nrect_x, rect_y, rect_widths, rect_heights = [], [], [], []\nrect_colors, rect_products, rect_regions, rect_values, rect_percentages = [], [], [], [], []\n\ncurrent_x = 0.0\nfor region in regions:\n    bar_width = widths[region]\n    bar_center_x = current_x + bar_width / 2\n    current_y = 0.0\n    region_total = region_totals[region]\n\n    for i, product in enumerate(products):\n        value = market_data[region][product]\n        height = value / region_total\n\n        rect_x.append(bar_center_x)\n        rect_y.append(current_y + height / 2)\n        rect_widths.append(bar_width * 0.98)\n        rect_heights.append(height)\n        rect_colors.append(IMPRINT[i])\n        rect_products.append(product)\n        rect_regions.append(region)\n        rect_values.append(value)\n        rect_percentages.append(f\"{height * 100:.1f}%\")\n\n        current_y += height\n\n    current_x += bar_width + bar_gap\n\n# Plot — extra headroom above (legend) and below (region labels) the [0, 1]\n# stacked-share band keeps both off the bar body, unlike a bar-body overlay.\np = figure(\n    width=3200,\n    height=1800,\n    title=\"marimekko-basic · bokeh · anyplot.ai\",\n    x_range=(-0.02, 1.02),\n    y_range=(-0.24, 1.30),\n    tools=\"\",\n    toolbar_location=None,\n    min_border_top=110,\n    min_border_bottom=40,\n    min_border_left=40,\n    min_border_right=40,\n)\n\n# One renderer per product (rather than a single factor-colored renderer) so\n# bokeh builds a real, auto-placed Legend from legend_label= instead of a\n# manual quad+text stand-in.\nrect_renderers = []\nfor i, product in enumerate(products):\n    idx = [j for j, prod in enumerate(rect_products) if prod == product]\n    product_source = ColumnDataSource(\n        data={\n            \"x\": [rect_x[j] for j in idx],\n            \"y\": [rect_y[j] for j in idx],\n            \"width\": [rect_widths[j] for j in idx],\n            \"height\": [rect_heights[j] for j in idx],\n            \"region\": [rect_regions[j] for j in idx],\n            \"product\": [product] * len(idx),\n            \"value\": [rect_values[j] for j in idx],\n            \"percentage\": [rect_percentages[j] for j in idx],\n        }\n    )\n    renderer = p.rect(\n        x=\"x\",\n        y=\"y\",\n        width=\"width\",\n        height=\"height\",\n        color=IMPRINT[i],\n        source=product_source,\n        line_color=PAGE_BG,\n        line_width=3,\n        legend_label=product,\n    )\n    rect_renderers.append(renderer)\n\nhover = HoverTool(\n    renderers=rect_renderers,\n    tooltips=[(\"Region\", \"@region\"), (\"Product\", \"@product\"), (\"Value\", \"$@value B\"), (\"Share\", \"@percentage\")],\n)\np.add_tools(hover)\n\n# Value labels on larger segments\nlabel_x, label_y, label_text = [], [], []\nfor i in range(len(rect_x)):\n    if rect_heights[i] > 0.12 and rect_widths[i] > 0.08:\n        label_x.append(rect_x[i])\n        label_y.append(rect_y[i])\n        label_text.append(f\"${rect_values[i]}B\")\n\nlabel_source = ColumnDataSource(data={\"x\": label_x, \"y\": label_y, \"text\": label_text})\np.add_layout(\n    LabelSet(\n        x=\"x\",\n        y=\"y\",\n        text=\"text\",\n        source=label_source,\n        text_align=\"center\",\n        text_baseline=\"middle\",\n        text_color=\"white\",\n        text_font_size=\"28pt\",\n        text_font_style=\"bold\",\n    )\n)\n\n# Region labels below each bar — the leading region gets a star + bold weight\n# as a focal point calling out the dominant market (data storytelling).\ncurrent_x = 0.0\nfor region in regions:\n    bar_width = widths[region]\n    is_leader = region == leading_region\n    text = f\"{'★ ' if is_leader else ''}{region}\\n(${region_totals[region]}B)\"\n    p.add_layout(\n        Label(\n            x=current_x + bar_width / 2,\n            y=-0.04,\n            text=text,\n            text_align=\"center\",\n            text_baseline=\"top\",\n            text_color=INK,\n            text_font_size=\"34pt\",\n            text_font_style=\"bold\" if is_leader else \"normal\",\n        )\n    )\n    current_x += bar_width + bar_gap\n\n# Style\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n# Match (not None) the frame outline to the page background — bokeh's headless\n# Chrome screenshot renders a faint default outline even with outline_line_color\n# unset, so blending it into the surface is the reliable way to hide it.\np.outline_line_color = PAGE_BG\n\np.title.text_font_size = \"50pt\"\np.title.align = \"center\"\np.title.text_color = INK\n\np.xaxis.visible = False\np.yaxis.visible = False\np.xgrid.visible = False\np.ygrid.visible = False\n\n# Auto-generated legend (from legend_label= above), pinned in the headroom\n# band above the bars so it never overlaps the data — fixes the previous\n# manual legend sitting on top of the Asia Pacific bar.\np.legend.location = \"top_center\"\np.legend.orientation = \"horizontal\"\np.legend.click_policy = \"hide\"\np.legend.background_fill_color = ELEVATED_BG\np.legend.border_line_color = INK_SOFT\np.legend.label_text_color = INK_SOFT\np.legend.label_text_font_size = \"34pt\"\np.legend.glyph_height = 34\np.legend.glyph_width = 34\np.legend.spacing = 24\np.legend.margin = 20\np.legend.padding = 16\n\n# Save — the interactive HTML is a required catalog artifact, and the PNG is\n# screenshotted with headless Chrome rather than bokeh.io.export_png, which\n# depends on a chromedriver snap shim unavailable in this environment.\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\nW, H = 3200, 1800\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\n# Headless Chrome's --window-size sets the OUTER window, which still reserves\n# a phantom title-bar height even headless — pin the viewport exactly via CDP\n# so the screenshot lands on the canonical 3200x1800 pixel target.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}