{"spec_id":"scatter-text","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nscatter-text: Scatter Plot with Text Labels Instead of Points\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, 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\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\n\n# Data: Simulated word embeddings for programming languages\nnp.random.seed(42)\n\nlabels = [\n    \"Python\",\n    \"JavaScript\",\n    \"Java\",\n    \"C++\",\n    \"Ruby\",\n    \"Go\",\n    \"Rust\",\n    \"Swift\",\n    \"Kotlin\",\n    \"TypeScript\",\n    \"PHP\",\n    \"Scala\",\n    \"R\",\n    \"Julia\",\n    \"Perl\",\n    \"Lua\",\n    \"Haskell\",\n    \"Clojure\",\n    \"Erlang\",\n    \"Elixir\",\n    \"Dart\",\n    \"MATLAB\",\n    \"Fortran\",\n    \"COBOL\",\n    \"Assembly\",\n    \"SQL\",\n    \"Bash\",\n    \"PowerShell\",\n    \"Groovy\",\n    \"F#\",\n]\n\nn = len(labels)\nx = np.zeros(n)\ny = np.zeros(n)\n\n# Modern general-purpose languages\nmodern = [0, 1, 2, 8, 9, 10, 18, 20]\nfor i in modern:\n    x[i] = np.random.normal(4, 1.2)\n    y[i] = np.random.normal(3, 1.2)\n\n# Systems languages\nsystems = [3, 5, 6, 7, 24]\nfor i in systems:\n    x[i] = np.random.normal(-3, 1.0)\n    y[i] = np.random.normal(4, 1.0)\n\n# Functional languages with improved spacing\nfunctional = [11, 16, 17, 19, 29]\nfor i, idx in enumerate(functional):\n    x[idx] = np.random.normal(-3.5 + i * 0.8, 0.5)\n    y[idx] = np.random.normal(-2.5 + i * 0.5, 0.5)\n\n# Data science / Scientific\nscientific = [4, 12, 13, 21]\nfor i in scientific:\n    x[i] = np.random.normal(1, 1.2)\n    y[i] = np.random.normal(-3.5, 1.0)\n\n# Scripting languages\nscripting = [14, 15, 26, 27]\nfor i in scripting:\n    x[i] = np.random.normal(-1, 1.5)\n    y[i] = np.random.normal(0.5, 1.5)\n\n# Legacy languages with adjusted positions to reduce COBOL/Erlang overlap\nlegacy = [22, 23, 25, 28]\nlegacy_x_offsets = [0, 0.8, -0.8, 0.4]  # Space COBOL away from Erlang\nfor idx, x_off in zip(legacy, legacy_x_offsets, strict=True):\n    x[idx] = np.random.normal(3 + x_off, 0.8)\n    y[idx] = np.random.normal(-1, 0.8)\n\n# Add jitter with reduced magnitude for COBOL/Erlang pair\njitter_x = np.random.normal(0, 0.15, n)\njitter_y = np.random.normal(0, 0.15, n)\n# Reduce jitter for COBOL (index 23) and Erlang (index 18)\njitter_x[[18, 23]] *= 0.5\njitter_y[[18, 23]] *= 0.5\nx += jitter_x\ny += jitter_y\n\n# Create data source\nsource = ColumnDataSource(data={\"x\": x, \"y\": y, \"labels\": labels})\n\n# Create figure\np = figure(\n    width=4800,\n    height=2700,\n    title=\"scatter-text · Python · bokeh · anyplot.ai\",\n    x_axis_label=\"Embedding Dimension 1\",\n    y_axis_label=\"Embedding Dimension 2\",\n    tools=\"pan,wheel_zoom,box_zoom,reset,save\",\n)\n\n# Style the figure\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\n\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\n\np.xaxis.major_label_text_font_size = \"18pt\"\np.yaxis.major_label_text_font_size = \"18pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\n\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\n# Grid styling\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# Define group membership for visual hierarchy\ngroups = {\n    \"modern\": [0, 1, 2, 8, 9, 10, 18, 20],\n    \"systems\": [3, 5, 6, 7, 24],\n    \"functional\": [11, 16, 17, 19, 29],\n    \"scientific\": [4, 12, 13, 21],\n    \"scripting\": [14, 15, 26, 27],\n    \"legacy\": [22, 23, 25, 28],\n}\n\n# Create per-group styling with visual hierarchy: modern prominent, others subtle\ngroup_alpha = {\n    \"modern\": 0.95,  # Primary group\n    \"systems\": 0.80,  # Subtle\n    \"functional\": 0.75,  # Subtle\n    \"scientific\": 0.85,  # Moderate\n    \"scripting\": 0.78,  # Subtle\n    \"legacy\": 0.73,  # Subtle\n}\n\ngroup_size = {\n    \"modern\": \"23pt\",  # Slightly larger\n    \"systems\": \"21pt\",  # Slightly smaller\n    \"functional\": \"20pt\",\n    \"scientific\": \"22pt\",\n    \"scripting\": \"20pt\",\n    \"legacy\": \"20pt\",\n}\n\n# Assign group info to data for styling\ngroup_membership = [\"\"] * n\nfor group_name, indices in groups.items():\n    for idx in indices:\n        group_membership[idx] = group_name\n\nsource.data[\"group\"] = group_membership\n\n# Add invisible scatter points for HoverTool\nhover = HoverTool(tooltips=[(\"Language\", \"@labels\"), (\"Group\", \"@group\")])\np.add_tools(hover)\np.scatter(\"x\", \"y\", source=source, size=1, alpha=0)\n\n# Add text labels with per-group visual hierarchy\nfor group_name, indices in groups.items():\n    group_data = ColumnDataSource(\n        data={\"x\": [x[i] for i in indices], \"y\": [y[i] for i in indices], \"labels\": [labels[i] for i in indices]}\n    )\n    text_labels = LabelSet(\n        x=\"x\",\n        y=\"y\",\n        text=\"labels\",\n        source=group_data,\n        text_font_size=group_size[group_name],\n        text_color=BRAND,\n        text_alpha=group_alpha[group_name],\n        text_align=\"center\",\n        text_baseline=\"middle\",\n    )\n    p.add_layout(text_labels)\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome using Selenium\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"}