{"spec_id":"lollipop-grouped","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nlollipop-grouped: Grouped Lollipop Chart\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Fix module shadowing: remove current directory from sys.path during imports\n_orig_path = sys.path[:]\nsys.path = [p for p in sys.path if p not in (\"\", \".\", os.getcwd())]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Legend, LegendItem\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Restore original path after imports\nsys.path = _orig_path\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# Okabe-Ito palette (first series is ALWAYS #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Quarterly revenue by product line across regions\nnp.random.seed(42)\n\ncategories = [\"North\", \"South\", \"East\", \"West\"]\nseries_names = [\"Electronics\", \"Clothing\", \"Food\", \"Home\"]\n\ndata = {\n    \"Electronics\": [85, 72, 91, 68],\n    \"Clothing\": [62, 78, 55, 71],\n    \"Food\": [45, 52, 48, 58],\n    \"Home\": [38, 41, 35, 47],\n}\n\n# Create figure\np = figure(\n    width=4800,\n    height=2700,\n    title=\"lollipop-grouped · Python · bokeh · anyplot.ai\",\n    x_axis_label=\"Region\",\n    y_axis_label=\"Revenue ($ Million)\",\n    x_range=(-1, len(categories) * (len(series_names) + 1)),\n    y_range=(0, 105),\n)\n\n# Plot lollipops for each series\nlegend_items = []\n\nfor series_idx, (series_name, color) in enumerate(zip(series_names, IMPRINT, strict=True)):\n    # Calculate x positions for this series\n    x_pos = [i * (len(series_names) + 1) + series_idx for i in range(len(categories))]\n    y_vals = data[series_name]\n\n    # Create stems (vertical lines from 0 to value)\n    for x, y in zip(x_pos, y_vals, strict=True):\n        stem_source = ColumnDataSource(data={\"x\": [x, x], \"y\": [0, y]})\n        p.line(x=\"x\", y=\"y\", source=stem_source, line_width=8, color=color, alpha=0.85)\n\n    # Create markers (circles)\n    marker_source = ColumnDataSource(data={\"x\": x_pos, \"y\": y_vals})\n    circle = p.scatter(\n        x=\"x\", y=\"y\", source=marker_source, size=45, color=color, alpha=0.95, line_color=PAGE_BG, line_width=4\n    )\n    legend_items.append(LegendItem(label=series_name, renderers=[circle]))\n\n# Add legend\nlegend = Legend(items=legend_items, location=\"top_right\")\nlegend.label_text_font_size = \"18pt\"\nlegend.glyph_height = 20\nlegend.glyph_width = 20\nlegend.spacing = 15\nlegend.padding = 20\nlegend.background_fill_color = ELEVATED_BG\nlegend.background_fill_alpha = 0.95\nlegend.border_line_color = INK_SOFT\nlegend.label_text_color = INK_SOFT\np.add_layout(legend)\n\n# Set x-axis tick labels (category names at group centers)\ngroup_centers = [i * (len(series_names) + 1) + 1.5 for i in range(len(categories))]\np.xaxis.ticker = group_centers\np.xaxis.major_label_overrides = dict(zip(group_centers, categories, strict=True))\n\n# Text sizing for 4800×2700 px\np.title.text_font_size = \"28pt\"\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.major_label_text_font_size = \"18pt\"\np.yaxis.major_label_text_font_size = \"18pt\"\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\np.outline_line_width = 2\n\np.title.text_color = INK\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.10\np.ygrid.grid_line_alpha = 0.10\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome\nW, H = 4800, 2700\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()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}