{"spec_id":"area-mountain-panorama","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\narea-mountain-panorama: Mountain Panorama Profile with Labeled Peaks\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-06-30\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.colors import LinearSegmentedColormap\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\"\nBRAND = \"#009E73\"\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — Wallis (Valais, Switzerland) panorama from Gornergrat, west → east sweep.\n# left_slope / right_slope: angular half-width (degrees) per unit drop on each flank.\n# Smaller = steeper spike. Asymmetry per peak gives varied alpine silhouette.\n# Matterhorn gets very narrow slopes to create the iconic sharp spike even though it is\n# not the tallest peak — its narrow tent makes it visually pierce the ridgeline.\npeaks = pd.DataFrame(\n    [\n        {\"name\": \"Weisshorn\", \"angle_deg\": 10.0, \"elevation_m\": 4506, \"left_slope\": 5.0, \"right_slope\": 7.0},\n        {\"name\": \"Zinalrothorn\", \"angle_deg\": 22.0, \"elevation_m\": 4221, \"left_slope\": 4.0, \"right_slope\": 5.5},\n        {\"name\": \"Ober Gabelhorn\", \"angle_deg\": 32.0, \"elevation_m\": 4063, \"left_slope\": 6.5, \"right_slope\": 4.5},\n        {\"name\": \"Dent Blanche\", \"angle_deg\": 44.0, \"elevation_m\": 4358, \"left_slope\": 5.5, \"right_slope\": 7.0},\n        {\"name\": \"Matterhorn\", \"angle_deg\": 62.0, \"elevation_m\": 4478, \"left_slope\": 2.8, \"right_slope\": 2.0},\n        {\"name\": \"Breithorn\", \"angle_deg\": 82.0, \"elevation_m\": 4164, \"left_slope\": 8.5, \"right_slope\": 6.5},\n        {\"name\": \"Pollux\", \"angle_deg\": 92.0, \"elevation_m\": 4092, \"left_slope\": 3.5, \"right_slope\": 4.5},\n        {\"name\": \"Castor\", \"angle_deg\": 99.0, \"elevation_m\": 4223, \"left_slope\": 3.5, \"right_slope\": 3.0},\n        {\"name\": \"Liskamm\", \"angle_deg\": 110.0, \"elevation_m\": 4527, \"left_slope\": 8.0, \"right_slope\": 5.5},\n        {\"name\": \"Dufourspitze\", \"angle_deg\": 124.0, \"elevation_m\": 4634, \"left_slope\": 6.5, \"right_slope\": 5.0},\n        {\"name\": \"Strahlhorn\", \"angle_deg\": 142.0, \"elevation_m\": 4190, \"left_slope\": 5.5, \"right_slope\": 6.0},\n        {\"name\": \"Rimpfischhorn\", \"angle_deg\": 152.0, \"elevation_m\": 4199, \"left_slope\": 4.0, \"right_slope\": 5.5},\n        {\"name\": \"Allalinhorn\", \"angle_deg\": 161.0, \"elevation_m\": 4027, \"left_slope\": 5.5, \"right_slope\": 4.0},\n        {\"name\": \"Alphubel\", \"angle_deg\": 171.0, \"elevation_m\": 4206, \"left_slope\": 6.0, \"right_slope\": 4.5},\n        {\"name\": \"Täschhorn\", \"angle_deg\": 181.0, \"elevation_m\": 4491, \"left_slope\": 4.5, \"right_slope\": 3.5},\n        {\"name\": \"Dom\", \"angle_deg\": 191.0, \"elevation_m\": 4545, \"left_slope\": 4.0, \"right_slope\": 5.5},\n    ]\n)\n\n# Build skyline as upper envelope of piecewise-linear tent functions over undulating valley floor.\n# Tent formula: bump = h * max(0, 1 - left_dist - right_dist)\n# Exactly one of {left_dist, right_dist} is nonzero at each sample — giving sharp apexes.\n# Saddles between peaks dip to the valley floor because tent is zero beyond slope*1 deg.\nnp.random.seed(42)\nsample_angles = np.linspace(-5.0, 205.0, 1800)\n\nvalley_floor = 2950 + 90 * np.sin(sample_angles * np.pi / 95.0 + 0.4) + 55 * np.cos(sample_angles * np.pi / 47.0 + 1.1)\n\nridge = np.copy(valley_floor)\nfor _, row in peaks.iterrows():\n    floor_at_peak = valley_floor[np.argmin(np.abs(sample_angles - row[\"angle_deg\"]))]\n    bump_height = row[\"elevation_m\"] - floor_at_peak\n    left_dist = np.clip((row[\"angle_deg\"] - sample_angles) / row[\"left_slope\"], 0.0, None)\n    right_dist = np.clip((sample_angles - row[\"angle_deg\"]) / row[\"right_slope\"], 0.0, None)\n    tent = bump_height * np.maximum(0.0, 1.0 - left_dist - right_dist)\n    ridge = np.maximum(ridge, valley_floor + tent)\n\n# Small rocky texture along ridgeline (jagged detail), tapered at panorama edges\ntexture = (\n    20 * np.sin(sample_angles * 1.7 + 0.3)\n    + 12 * np.sin(sample_angles * 3.1 + 1.7)\n    + np.random.normal(0, 9, size=sample_angles.shape)\n)\nedge_taper = np.clip((sample_angles - 0) / 6, 0, 1) * np.clip((200 - sample_angles) / 6, 0, 1)\nridge = ridge + texture * edge_taper\n\nskyline = pd.DataFrame({\"angle_deg\": sample_angles, \"elevation_m\": ridge})\n\n# Canvas: 3200×1800 px (figsize=(8,4.5) × dpi=400)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nY_FLOOR = 2500\nLABEL_BASE_Y = 4880\nLABEL_STAGGER = 200\nY_TOP = LABEL_BASE_Y + 2 * LABEL_STAGGER + 350  # 5630\n\n# Dusk sky gradient above ridgeline — the mountain silhouette (zorder=2) covers below the ridge;\n# the gradient is visible in the sky and annotation zones above it.\nsky_colors = (\n    [\"#FDDCB0\", \"#E8A870\", \"#A8C8E0\"]  # warm amber → golden → soft sky blue\n    if THEME == \"light\"\n    else [\"#6B2040\", \"#2A1860\", \"#0A1428\"]  # magenta-purple → indigo → near-black\n)\nsky_cmap = LinearSegmentedColormap.from_list(\"sky_dusk\", sky_colors)\nsky_arr = np.linspace(0, 1, 256).reshape(-1, 1)\nax.imshow(sky_arr, aspect=\"auto\", cmap=sky_cmap, origin=\"lower\", extent=[0, 200, Y_FLOOR, Y_TOP], zorder=0, alpha=0.6)\n\n# Filled mountain silhouette + crisp ridgeline edge via sns.lineplot\nax.fill_between(skyline[\"angle_deg\"], skyline[\"elevation_m\"], Y_FLOOR, color=BRAND, alpha=1.0, linewidth=0, zorder=2)\nsns.lineplot(data=skyline, x=\"angle_deg\", y=\"elevation_m\", color=BRAND, linewidth=1.2, ax=ax, legend=False, zorder=2)\n\n# 3-level stagger: prevents label collisions in dense clusters (Breithorn/Pollux/Castor etc.)\npeak_angles = peaks[\"angle_deg\"].values\nlabel_levels = np.zeros(len(peaks), dtype=int)\nfor i in range(1, len(peaks)):\n    for lvl in range(3):\n        conflict = any(abs(peak_angles[j] - peak_angles[i]) < 20 and label_levels[j] == lvl for j in range(i))\n        if not conflict:\n            label_levels[i] = lvl\n            break\n    else:\n        label_levels[i] = i % 3\n\n# Peak labels with semi-transparent leader lines\nfor i, (_, row) in enumerate(peaks.iterrows()):\n    is_anchor = row[\"name\"] == \"Matterhorn\"\n    label_y = LABEL_BASE_Y + label_levels[i] * LABEL_STAGGER\n    elev_y = label_y - 130\n    leader_top = elev_y - 30\n\n    ax.plot(\n        [row[\"angle_deg\"], row[\"angle_deg\"]],\n        [row[\"elevation_m\"], leader_top],\n        color=INK_SOFT,\n        linewidth=0.8,\n        alpha=0.55,\n        zorder=3,\n    )\n    ax.text(\n        row[\"angle_deg\"],\n        label_y,\n        row[\"name\"],\n        fontsize=9 if is_anchor else 8,\n        fontweight=\"semibold\" if is_anchor else \"regular\",\n        color=INK,\n        ha=\"center\",\n        va=\"bottom\",\n        zorder=4,\n    )\n    ax.text(\n        row[\"angle_deg\"],\n        elev_y,\n        f\"{int(row['elevation_m'])} m\",\n        fontsize=7,\n        color=INK_MUTED,\n        ha=\"center\",\n        va=\"bottom\",\n        zorder=4,\n    )\n\n# Matterhorn focal marker — open circle at summit via sns.scatterplot\nmatterhorn = peaks.loc[peaks[\"name\"] == \"Matterhorn\"].iloc[0]\nsns.scatterplot(\n    x=[matterhorn[\"angle_deg\"]],\n    y=[matterhorn[\"elevation_m\"]],\n    s=90,\n    color=PAGE_BG,\n    edgecolor=BRAND,\n    linewidth=2.0,\n    ax=ax,\n    zorder=6,\n    legend=False,\n)\n\n# Axes style\nax.set_xlim(0, 200)\nax.set_ylim(Y_FLOOR, Y_TOP)\nax.set_xlabel(\"Compass bearing\", fontsize=10, color=INK)\nax.set_ylabel(\"Elevation (m)\", fontsize=10, color=INK)\nax.set_title(\n    \"area-mountain-panorama · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK, pad=10\n)\n\nax.set_xticks([0, 50, 100, 150, 200])\nax.set_xticklabels([\"W\", \"SW\", \"S\", \"SE\", \"E\"])\n# Y ticks only in the data range — no grid lines extending into the annotation zone\nax.set_yticks([2500, 3000, 3500, 4000, 4500])\nax.tick_params(axis=\"x\", labelsize=8, colors=INK_SOFT, length=0)\nax.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT, length=0)\n\nsns.despine(ax=ax, top=True, right=True)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nfig.subplots_adjust(left=0.09, right=0.97, top=0.91, bottom=0.11)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}