{"spec_id":"area-mountain-panorama","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\narea-mountain-panorama: Mountain Panorama Profile with Labeled Peaks\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-06-30\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Remove script's own directory from sys.path to prevent self-shadowing\n# (this file is named bokeh.py; without this, `import bokeh` would find itself)\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _here]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, FixedTicker, HoverTool, Label\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\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — always first series\n\n# Atmospheric sky gradient endpoints per theme\nSKY_TOP = \"#4A8EC4\" if THEME == \"light\" else \"#081424\"\nSKY_HORIZON = \"#D4EBF7\" if THEME == \"light\" else \"#1C2E45\"\n\n# Data — Wallis (Valais, Switzerland) summit panorama, ordered W → E\npeaks = [\n    (\"Weisshorn\", 8, 4506),\n    (\"Zinalrothorn\", 20, 4221),\n    (\"Ober Gabelhorn\", 31, 4063),\n    (\"Dent Blanche\", 42, 4358),\n    (\"Matterhorn\", 58, 4478),\n    (\"Breithorn\", 72, 4164),\n    (\"Pollux\", 81, 4092),\n    (\"Castor\", 89, 4223),\n    (\"Liskamm\", 97, 4527),\n    (\"Dufourspitze\", 109, 4634),\n    (\"Strahlhorn\", 121, 4190),\n    (\"Rimpfischhorn\", 132, 4199),\n    (\"Allalinhorn\", 142, 4027),\n    (\"Alphubel\", 152, 4206),\n    (\"Täschhorn\", 162, 4491),\n    (\"Dom\", 174, 4545),\n]\n\n# Build ridgeline control points: peaks alternating with saddles (cols)\nnp.random.seed(42)\nctrl_x = [-3.0]\nctrl_y = [3250.0]\nfor i, (_, ang, el) in enumerate(peaks):\n    ctrl_x.append(float(ang))\n    ctrl_y.append(float(el))\n    if i < len(peaks) - 1:\n        next_ang = peaks[i + 1][1]\n        next_el = peaks[i + 1][2]\n        col_ang = (ang + next_ang) / 2 + np.random.uniform(-1.2, 1.2)\n        col_drop = np.random.uniform(420, 820)\n        col_el = min(el, next_el) - col_drop\n        ctrl_x.append(float(col_ang))\n        ctrl_y.append(float(col_el))\nctrl_x.append(184.0)\nctrl_y.append(3350.0)\nctrl_x = np.array(ctrl_x)\nctrl_y = np.array(ctrl_y)\n\n# Piecewise-linear ridgeline with fractal jitter — sharp triangular peaks\n# Uses linear interpolation (not cosine smoothstep) for pointed alpine apexes;\n# tapered Gaussian noise adds rocky texture while preserving clean summit tips.\nridge_x_segs = []\nridge_y_segs = []\nfor i in range(len(ctrl_x) - 1):\n    n = 80\n    last = i == len(ctrl_x) - 2\n    t = np.linspace(0.0, 1.0, n, endpoint=last)\n    y_linear = ctrl_y[i] + (ctrl_y[i + 1] - ctrl_y[i]) * t\n    seg_dx = abs(ctrl_x[i + 1] - ctrl_x[i])\n    jitter_scale = 28 * np.sqrt(seg_dx / 8.0)\n    taper = np.sin(np.pi * t)  # 0 at endpoints → no jitter at exact summit/col\n    jitter = np.random.normal(0, jitter_scale, n) * taper\n    ridge_x_segs.append(ctrl_x[i] + (ctrl_x[i + 1] - ctrl_x[i]) * t)\n    ridge_y_segs.append(y_linear + jitter)\n\nridge_x = np.concatenate(ridge_x_segs)\nridge_y = np.concatenate(ridge_y_segs)\n\n# Anchor silhouette polygon at y-floor\nY_FLOOR = 2500\nY_TOP = 5600\npoly_x = np.concatenate([[ridge_x[0]], ridge_x, [ridge_x[-1]]])\npoly_y = np.concatenate([[Y_FLOOR], ridge_y, [Y_FLOOR]])\n\n# 4 label tiers — 3-previous penalty cycles all 4 evenly, spreading eastern cluster\nLEVEL_TIERS = [4880, 5040, 5200, 5360]\ntier_assignments = []\nfor i in range(len(peaks)):\n    prev_tier = tier_assignments[i - 1] if i > 0 else -1\n    prev2_tier = tier_assignments[i - 2] if i > 1 else -1\n    prev3_tier = tier_assignments[i - 3] if i > 2 else -1\n    scores = []\n    for t_idx in range(4):\n        penalty = 0\n        if t_idx == prev_tier:\n            penalty += 4\n        if t_idx == prev2_tier:\n            penalty += 2\n        if t_idx == prev3_tier:\n            penalty += 1\n        scores.append(penalty)\n    tier_assignments.append(int(np.argmin(scores)))\n\n# Canvas — hard rule: 3200×1800 landscape\nW, H = 3200, 1800\n\np = figure(\n    width=W,\n    height=H,\n    title=\"Wallis 4000ers · area-mountain-panorama · bokeh · anyplot.ai\",\n    y_axis_label=\"Elevation (m)\",\n    x_range=(-3, 184),\n    y_range=(Y_FLOOR, Y_TOP),\n    background_fill_color=PAGE_BG,\n    border_fill_color=PAGE_BG,\n    toolbar_location=None,  # disable toolbar — adds ~30-50px above canvas in PNG\n    min_border_bottom=60,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n\ndef _lerp_hex(c0, c1, t):\n    r0, g0, b0 = [int(c0[j : j + 2], 16) for j in (1, 3, 5)]\n    r1, g1, b1 = [int(c1[j : j + 2], 16) for j in (1, 3, 5)]\n    return \"#{:02X}{:02X}{:02X}\".format(int(r0 + t * (r1 - r0)), int(g0 + t * (g1 - g0)), int(b0 + t * (b1 - b0)))\n\n\n# Atmospheric sky gradient — drawn first so mountain silhouette overlaps correctly\nN_SKY = 60\nfor k in range(N_SKY):\n    t0, t1 = k / N_SKY, (k + 1) / N_SKY\n    y0 = Y_FLOOR + t0 * (Y_TOP - Y_FLOOR)\n    y1 = Y_FLOOR + t1 * (Y_TOP - Y_FLOOR)\n    color = _lerp_hex(SKY_HORIZON, SKY_TOP, t0)\n    p.quad(top=[y1], bottom=[y0], left=[-3.0], right=[184.0], color=color, line_color=None)\n\n# Mountain silhouette — Imprint palette position 1 (brand green, always first series)\np.patch(poly_x, poly_y, fill_color=BRAND, line_color=BRAND, line_width=2)\n\n# Peak labels, leader lines, summit dots\npeak_names, peak_angles, peak_elevs = [], [], []\nlabels = []\n\nfor i, (name, ang, el) in enumerate(peaks):\n    peak_names.append(name)\n    peak_angles.append(ang)\n    peak_elevs.append(el)\n\n    tier_idx = tier_assignments[i]\n    label_y = LEVEL_TIERS[tier_idx]\n    is_focal = name == \"Matterhorn\"\n    leader_color = INK if is_focal else INK_SOFT\n    leader_alpha = 0.9 if is_focal else 0.55\n    leader_width = 3.0 if is_focal else 1.8\n\n    p.line(\n        [ang, ang], [el + 25, label_y - 90], line_color=leader_color, line_alpha=leader_alpha, line_width=leader_width\n    )\n\n    dot_size = 28 if is_focal else 20\n    p.scatter([ang], [el], size=dot_size, fill_color=INK if is_focal else INK_SOFT, line_color=PAGE_BG, line_width=2)\n\n    labels.append(\n        Label(\n            x=ang,\n            y=label_y + 55,\n            text=name,\n            text_color=INK,\n            text_font_size=\"26pt\" if is_focal else \"21pt\",\n            text_font_style=\"bold\" if is_focal else \"normal\",\n            text_align=\"center\",\n            text_baseline=\"bottom\",\n        )\n    )\n    labels.append(\n        Label(\n            x=ang,\n            y=label_y + 30,\n            text=f\"{el:,} m\",\n            text_color=INK_SOFT,\n            text_font_size=\"18pt\",\n            text_align=\"center\",\n            text_baseline=\"top\",\n        )\n    )\n\n# Matterhorn focal callout — 19pt INK_SOFT (stronger than prior 16pt INK_MUTED)\nlabels.append(\n    Label(\n        x=58,\n        y=4290,\n        text=\"focal peak · 4478 m a.s.l.\",\n        text_color=INK_SOFT,\n        text_font_size=\"19pt\",\n        text_font_style=\"italic\",\n        text_align=\"center\",\n        text_baseline=\"bottom\",\n    )\n)\n\nfor lbl in labels:\n    p.add_layout(lbl)\n\n# HoverTool — invisible hit targets at each summit for interactive HTML artifact\npeak_source = ColumnDataSource(\n    {\"x\": peak_angles, \"y\": peak_elevs, \"name\": peak_names, \"elevation\": peak_elevs, \"bearing\": peak_angles}\n)\nhover_targets = p.scatter(\"x\", \"y\", source=peak_source, size=30, fill_alpha=0.0, line_alpha=0.0)\np.add_tools(\n    HoverTool(\n        renderers=[hover_targets],\n        tooltips=[(\"Peak\", \"@name\"), (\"Elevation\", \"@elevation{0,0} m\"), (\"Bearing\", \"@bearing°\")],\n    )\n)\n\n# Typography — per bokeh.md sizing for 3200×1800\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.title.text_font_style = \"normal\"\np.title.align = \"center\"\n\np.yaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_color = INK\np.yaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.yaxis.minor_tick_line_color = None\np.yaxis.ticker = FixedTicker(ticks=[2500, 3000, 3500, 4000, 4500, 5000])\np.yaxis.axis_label_standoff = 18\n\n# Hide x-axis — bearing angles would clutter the panorama silhouette\np.xaxis.visible = False\n\n# Grid: y-only, very subtle\np.outline_line_color = None\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.10\n\n# Save interactive HTML (required catalog artifact)\nhtml_path = Path(f\"plot-{THEME}.html\")\noutput_file(str(html_path), title=\"Wallis 4000ers · area-mountain-panorama · bokeh · anyplot.ai\")\nsave(p)\n\n# Inject body background CSS to prevent thin border artifact in headless-Chrome screenshot\nhtml_content = html_path.read_text()\nbody_style = f\"<style>body{{margin:0;padding:0;background:{PAGE_BG};}}</style>\"\nhtml_content = html_content.replace(\"</head>\", f\"{body_style}\\n</head>\", 1)\nhtml_path.write_text(html_content)\n\n# Screenshot via headless Chrome — use CDP to set exact viewport to match figure dimensions\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.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ndriver.get(f\"file://{html_path.resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}