{"spec_id":"area-elevation-profile","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\narea-elevation-profile: Terrain Elevation Profile Along Transect\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove script dir from sys.path so 'import plotly' resolves to the installed package\n_sd = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _sd]\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens (Imprint palette — theme-adaptive chrome)\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)\"\n\n# Imprint categorical palette\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data\nnp.random.seed(42)\ndistance = np.linspace(0, 120, 300)\nkeypoints_dist = [0, 10, 18, 28, 35, 45, 58, 68, 82, 92, 100, 110, 120]\nkeypoints_elev = [1034, 1200, 2265, 1800, 2681, 1900, 2076, 1400, 1100, 1600, 2061, 1500, 1274]\nelevation = np.interp(distance, keypoints_dist, keypoints_elev)\nelevation += np.random.normal(0, 12, len(distance))\n\n# Slope categories\nslope = np.gradient(elevation, distance)\nabs_slope = np.abs(slope)\nslope_labels = [\"gentle\" if s < 15 else \"moderate\" if s < 35 else \"steep\" for s in abs_slope]\n\n# Slope palette — Imprint semantic: green=easy, ochre=moderate, red=steep/difficult\nslope_palette = {\n    \"gentle\": IMPRINT_PALETTE[0],  # #009E73 brand green\n    \"moderate\": IMPRINT_PALETTE[3],  # #BD8233 ochre\n    \"steep\": IMPRINT_PALETTE[4],  # #AE3030 matte red\n}\n\nlandmarks = [\n    {\"name\": \"Grindelwald\", \"distance\": 0.0, \"elevation\": 1034},\n    {\"name\": \"Bachalpsee\", \"distance\": 18.0, \"elevation\": 2265},\n    {\"name\": \"Faulhorn\", \"distance\": 35.0, \"elevation\": 2681},\n    {\"name\": \"Schynige Platte\", \"distance\": 58.0, \"elevation\": 2076},\n    {\"name\": \"Männlichen\", \"distance\": 82.0, \"elevation\": 1100},\n    {\"name\": \"Kleine Scheidegg\", \"distance\": 100.0, \"elevation\": 2061},\n    {\"name\": \"Wengen\", \"distance\": 120.0, \"elevation\": 1274},\n]\n\n# Vertical exaggeration (inner plot area ~680 × 300 at this layout)\nplot_h_px, plot_w_px = 300, 680\nx_range_m = 127 * 1000\ny_range_m = 3200\nvert_exag_display = round((x_range_m / plot_w_px) / (y_range_m / plot_h_px))\n\n# Theme-adaptive fill opacities for terrain silhouette\nfill_bot = 0.08 if THEME == \"light\" else 0.15\nfill_top = 0.48 if THEME == \"light\" else 0.62\narrow_color = \"rgba(68, 103, 163, 0.40)\" if THEME == \"light\" else \"rgba(68, 103, 163, 0.65)\"\nref_line_color = \"rgba(68, 103, 163, 0.22)\" if THEME == \"light\" else \"rgba(68, 103, 163, 0.45)\"\n\n# Plot\nfig = go.Figure()\n\n# Terrain fill with vertical gradient — Imprint blue #4467A3 = rgb(68, 103, 163)\nfig.add_trace(\n    go.Scatter(\n        x=distance,\n        y=elevation,\n        fill=\"tozeroy\",\n        fillgradient={\n            \"type\": \"vertical\",\n            \"colorscale\": [\n                [0.0, f\"rgba(68, 103, 163, {fill_bot})\"],\n                [0.5, \"rgba(68, 103, 163, 0.28)\"],\n                [1.0, f\"rgba(68, 103, 163, {fill_top})\"],\n            ],\n        },\n        line={\"color\": IMPRINT_PALETTE[2], \"width\": 2},\n        mode=\"lines\",\n        name=\"Elevation\",\n        hovertemplate=\"Distance: %{x:.1f} km<br>Elevation: %{y:.0f} m<extra></extra>\",\n    )\n)\n\n# Slope-colored line overlay — group consecutive same-category segments\ni = 0\nwhile i < len(distance) - 1:\n    label = slope_labels[i]\n    j = i + 1\n    while j < len(distance) - 1 and slope_labels[j] == label:\n        j += 1\n    fig.add_trace(\n        go.Scatter(\n            x=distance[i : j + 1],\n            y=elevation[i : j + 1],\n            mode=\"lines\",\n            line={\"color\": slope_palette[label], \"width\": 3},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        )\n    )\n    i = j\n\n# Landmark circle markers\nfig.add_trace(\n    go.Scatter(\n        x=[lm[\"distance\"] for lm in landmarks],\n        y=[lm[\"elevation\"] for lm in landmarks],\n        mode=\"markers\",\n        marker={\"size\": 12, \"color\": IMPRINT_PALETTE[2], \"line\": {\"color\": PAGE_BG, \"width\": 2}, \"symbol\": \"circle\"},\n        showlegend=False,\n        hovertemplate=\"%{text}<br>Distance: %{x:.1f} km<br>Elevation: %{y:.0f} m<extra></extra>\",\n        text=[lm[\"name\"] for lm in landmarks],\n    )\n)\n\n# Annotations and vertical reference lines\noffsets_y = [50, 50, 50, 50, -50, 50, 50]\narrow_ay = [-40, -40, -40, -40, 40, -40, -40]\nanchors_y = [\"bottom\", \"bottom\", \"bottom\", \"bottom\", \"top\", \"bottom\", \"bottom\"]\nanchors_x = [\"left\", \"center\", \"center\", \"center\", \"center\", \"center\", \"right\"]\nannotations = []\n\nfor idx, lm in enumerate(landmarks):\n    fig.add_shape(\n        type=\"line\",\n        x0=lm[\"distance\"],\n        x1=lm[\"distance\"],\n        y0=0,\n        y1=lm[\"elevation\"],\n        line={\"color\": ref_line_color, \"width\": 1, \"dash\": \"dot\"},\n    )\n    annotations.append(\n        {\n            \"x\": lm[\"distance\"],\n            \"y\": lm[\"elevation\"] + offsets_y[idx],\n            \"text\": f\"<b>{lm['name']}</b><br>{lm['elevation']:,} m\",\n            \"showarrow\": True,\n            \"arrowhead\": 0,\n            \"arrowwidth\": 1,\n            \"arrowcolor\": arrow_color,\n            \"ax\": 0,\n            \"ay\": arrow_ay[idx],\n            \"font\": {\"size\": 11, \"color\": INK},\n            \"align\": \"center\",\n            \"xanchor\": anchors_x[idx],\n            \"yanchor\": anchors_y[idx],\n        }\n    )\n\n# Vertical exaggeration note\nannotations.append(\n    {\n        \"x\": 1.0,\n        \"y\": 0.0,\n        \"xref\": \"paper\",\n        \"yref\": \"paper\",\n        \"text\": f\"Vertical exaggeration: ~{vert_exag_display}x\",\n        \"showarrow\": False,\n        \"font\": {\"size\": 10, \"color\": INK_MUTED},\n        \"xanchor\": \"right\",\n        \"yanchor\": \"bottom\",\n    }\n)\n\n# Slope legend\nannotations.append(\n    {\n        \"x\": 0.0,\n        \"y\": 0.0,\n        \"xref\": \"paper\",\n        \"yref\": \"paper\",\n        \"text\": (\n            f'<span style=\"color:{slope_palette[\"gentle\"]}\">█</span> Gentle  '\n            f'<span style=\"color:{slope_palette[\"moderate\"]}\">█</span> Moderate  '\n            f'<span style=\"color:{slope_palette[\"steep\"]}\">█</span> Steep'\n        ),\n        \"showarrow\": False,\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"xanchor\": \"left\",\n        \"yanchor\": \"bottom\",\n    }\n)\n\n# Title font size scaled to title length (floor: 11px per plotly guide)\ntitle = \"Bernese Oberland Traverse · area-elevation-profile · python · plotly · anyplot.ai\"\ntitle_fontsize = max(11, round(16 * 67 / len(title)))\n\n# Style\nfig.update_layout(\n    autosize=False,\n    title={\"text\": title, \"font\": {\"size\": title_fontsize, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Distance (km)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"range\": [-2, 125],\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"ticks\": \"\",\n    },\n    yaxis={\n        \"title\": {\"text\": \"Elevation (m)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"range\": [0, 3200],\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"ticks\": \"\",\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    showlegend=False,\n    annotations=annotations,\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 70},\n    template=\"plotly_white\",\n)\n\n# Save (3200 × 1800 landscape canvas)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}