{"spec_id":"area-mountain-panorama","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\narea-mountain-panorama: Mountain Panorama Profile with Labeled Peaks\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 88/100 | Updated: 2026-06-30\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — mountain silhouette\nAMBER = \"#DDCC77\"  # semantic anchor — focal peak (Matterhorn) spotlight\n\n# Sky fill — Imprint cyan (light) / Imprint blue (dark) for atmospheric mood per spec\nSKY_FILL = \"rgba(42,188,205,0.12)\" if THEME == \"light\" else \"rgba(68,103,163,0.22)\"\n\n# Data — Wallis (Valais) panorama: peaks ordered along a 0–180° angular sweep\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 piecewise-linear ridgeline: peaks connected by angular V-shaped saddles\n# No smoothing — each segment is straight to produce the alpine angular silhouette\nnp.random.seed(42)\n\n# Accumulate (x, y) control points for the full ridgeline\nridge_x = [-3.0]\nridge_y = [3250.0]\n\nfor i, (_, ang, el) in enumerate(peaks):\n    # Approach flank: one intermediate point on the way up to the summit\n    if len(ridge_x) > 1:\n        prev_x = ridge_x[-1]\n        prev_y = ridge_y[-1]\n        # Steep asymmetric approach — put the inflection point close to the summit\n        frac = np.random.uniform(0.65, 0.85)\n        mid_x = prev_x + (ang - prev_x) * frac\n        mid_y = prev_y + (el - prev_y) * np.random.uniform(0.15, 0.40)\n        ridge_x.append(float(mid_x))\n        ridge_y.append(float(mid_y))\n\n    # Summit apex\n    ridge_x.append(float(ang))\n    ridge_y.append(float(el))\n\n    if i < len(peaks) - 1:\n        next_ang = peaks[i + 1][1]\n        next_el = peaks[i + 1][2]\n        # Saddle (col) between this peak and the next — sharp V bottom\n        col_ang = (ang + next_ang) / 2 + np.random.uniform(-1.5, 1.5)\n        col_drop = np.random.uniform(420, 820)\n        col_el = min(el, next_el) - col_drop\n        # Descent: straight to col\n        ridge_x.append(float(col_ang))\n        ridge_y.append(float(col_el))\n\nridge_x.append(184.0)\nridge_y.append(3350.0)\n\nridge_x = np.array(ridge_x)\nridge_y = np.array(ridge_y)\n\nY_FLOOR = 2500\nY_TOP = 5650\n\n# Anchor the silhouette polygon at the lower edge of the visible y-range\npoly_x = np.concatenate([[ridge_x[0]], ridge_x, [ridge_x[-1]]])\npoly_y = np.concatenate([[Y_FLOOR], ridge_y, [Y_FLOOR]])\n\n# Sky polygon above the ridgeline (fills from ridgeline to Y_TOP)\nsky_x = np.concatenate([[ridge_x[0]], ridge_x, [ridge_x[-1]]])\nsky_y = np.concatenate([[Y_TOP], ridge_y, [Y_TOP]])\n\n# Assign label tiers — 6 tiers with interleaved (0,3,1,4,2,5) pattern so that\n# consecutive peaks always land 280-420 m apart vertically, avoiding overlap\n# even when peaks are only 8-10° apart horizontally.\nLABEL_TIERS = [4800, 4940, 5080, 5220, 5360, 5500]\nTIER_ORDER = [0, 3, 1, 4, 2, 5]  # interleave low/high for max vertical spread\n\nlabel_tiers = [LABEL_TIERS[TIER_ORDER[i % len(TIER_ORDER)]] for i in range(len(peaks))]\n\n# Plot\nfig = go.Figure()\n\n# Sky fill behind silhouette — Imprint palette atmospheric tint per spec\nfig.add_trace(\n    go.Scatter(\n        x=sky_x,\n        y=sky_y,\n        mode=\"lines\",\n        line={\"color\": \"rgba(0,0,0,0)\", \"width\": 0},\n        fill=\"toself\",\n        fillcolor=SKY_FILL,\n        hoverinfo=\"skip\",\n        showlegend=False,\n    )\n)\n\n# Mountain silhouette (first categorical series — Imprint brand green)\nfig.add_trace(\n    go.Scatter(\n        x=poly_x,\n        y=poly_y,\n        mode=\"lines\",\n        line={\"color\": BRAND, \"width\": 2},\n        fill=\"toself\",\n        fillcolor=BRAND,\n        hoverinfo=\"skip\",\n        showlegend=False,\n    )\n)\n\n# Leader lines + peak markers + labels\nannotations = []\nfor i, (name, ang, el) in enumerate(peaks):\n    label_y = label_tiers[i]\n    is_focal = name == \"Matterhorn\"\n\n    # Leader line (thin, theme-adaptive)\n    fig.add_trace(\n        go.Scatter(\n            x=[ang, ang],\n            y=[el + 25, label_y - 60],\n            mode=\"lines\",\n            line={\"color\": INK_SOFT, \"width\": 1},\n            hoverinfo=\"skip\",\n            showlegend=False,\n        )\n    )\n\n    # Summit dot — Matterhorn gets amber focal treatment for visual distinction\n    dot_color = AMBER if is_focal else INK_SOFT\n    dot_size = 12 if is_focal else 6\n    fig.add_trace(\n        go.Scatter(\n            x=[ang],\n            y=[el],\n            mode=\"markers\",\n            marker={\"size\": dot_size, \"color\": dot_color, \"line\": {\"width\": 0}},\n            hoverinfo=\"skip\",\n            showlegend=False,\n        )\n    )\n\n    # Label: name on top, elevation below — compact fonts to prevent truncation\n    name_size = 11 if is_focal else 8\n    weight = \"700\" if is_focal else \"600\"\n    annotations.append(\n        {\n            \"x\": ang,\n            \"y\": label_y,\n            \"text\": (\n                f\"<span style='font-size:{name_size}px;font-weight:{weight};color:{INK}'>{name}</span><br>\"\n                f\"<span style='font-size:8px;color:{INK_MUTED}'>{el:,} m</span>\"\n            ),\n            \"showarrow\": False,\n            \"align\": \"center\",\n            \"xanchor\": \"center\",\n            \"yanchor\": \"middle\",\n        }\n    )\n\n# Subtitle annotation\nannotations.append(\n    {\n        \"x\": 0.5,\n        \"y\": 1.07,\n        \"xref\": \"paper\",\n        \"yref\": \"paper\",\n        \"text\": f\"<span style='color:{INK_SOFT}'>Wallis panorama — sixteen 4 000 m peaks of the Pennine Alps</span>\",\n        \"showarrow\": False,\n        \"font\": {\"size\": 15},\n        \"xanchor\": \"center\",\n    }\n)\n\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": \"area-mountain-panorama · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 18, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n        \"y\": 0.97,\n    },\n    annotations=annotations,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    xaxis={\n        \"range\": [-3, 184],\n        \"showgrid\": False,\n        \"showticklabels\": False,\n        \"ticks\": \"\",\n        \"zeroline\": False,\n        \"showline\": False,\n        \"fixedrange\": True,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Elevation (m)\", \"font\": {\"size\": 16, \"color\": INK}},\n        \"tickfont\": {\"size\": 14, \"color\": INK_SOFT},\n        \"tickvals\": [2500, 3000, 3500, 4000, 4500, 5000],\n        \"ticksuffix\": \"  \",\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"showgrid\": True,\n        \"zeroline\": False,\n        \"showline\": False,\n        \"ticks\": \"\",\n        \"range\": [Y_FLOOR, Y_TOP],\n    },\n    margin={\"l\": 100, \"r\": 60, \"t\": 130, \"b\": 50},\n)\n\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}