{"spec_id":"area-mountain-panorama","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\narea-mountain-panorama: Mountain Panorama Profile with Labeled Peaks\nLibrary: letsplot 4.11.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-30\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_area,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — ridge outline\n\n# Mountain silhouette fill — dark slate for evening/dusk photo feel\nMOUNTAIN_FILL = \"#2C3E50\"\n# Sky panel background — atmospheric context above the ridgeline\nSKY_COLOR = \"#B8D4E8\" if THEME == \"light\" else \"#0B1726\"\n\n# Data — Wallis (Valais) panorama from Gornergrat, WSW to NE\npeak_records = [\n    (\"Matterhorn\", 22, 4478),\n    (\"Dent Blanche\", 46, 4358),\n    (\"Ober Gabelhorn\", 64, 4063),\n    (\"Zinalrothorn\", 80, 4221),\n    (\"Weisshorn\", 96, 4506),\n    (\"Dom\", 122, 4545),\n    (\"Täschhorn\", 132, 4491),\n    (\"Alphubel\", 144, 4206),\n    (\"Allalinhorn\", 156, 4027),\n    (\"Rimpfischhorn\", 170, 4199),\n    (\"Strahlhorn\", 184, 4190),\n    (\"Monte Rosa\", 212, 4634),\n    (\"Liskamm\", 230, 4527),\n    (\"Castor\", 244, 4223),\n    (\"Pollux\", 252, 4092),\n    (\"Breithorn\", 268, 4164),\n]\npeaks_df = pd.DataFrame(peak_records, columns=[\"name\", \"angle\", \"elev\"])\npeaks_df[\"elev_text\"] = peaks_df[\"elev\"].astype(str) + \" m\"\n\n# Skyline — piecewise-linear tent functions (triangular peaks, NOT Gaussians)\nnp.random.seed(42)\nn_samples = 1600\nangle = np.linspace(0, 290, n_samples)\nbase_elev = 3000.0\nskyline = np.full_like(angle, base_elev)\n\n# Asymmetric flank widths (left_deg, right_deg) per peak — steep and angular\npeak_widths = [\n    (11, 14),  # Matterhorn — steep left flank (iconic pyramid)\n    (16, 12),  # Dent Blanche\n    (10, 8),  # Ober Gabelhorn — compact\n    (12, 10),  # Zinalrothorn\n    (14, 16),  # Weisshorn\n    (12, 10),  # Dom\n    (8, 6),  # Täschhorn — narrow and steep\n    (10, 12),  # Alphubel\n    (10, 8),  # Allalinhorn\n    (9, 11),  # Rimpfischhorn\n    (12, 10),  # Strahlhorn\n    (16, 14),  # Monte Rosa — broad massif\n    (10, 12),  # Liskamm\n    (7, 7),  # Castor — symmetric\n    (6, 8),  # Pollux\n    (14, 10),  # Breithorn\n]\n\nfor i, (_, p) in enumerate(peaks_df.iterrows()):\n    lw, rw = peak_widths[i]\n    left_rise = base_elev + (p[\"elev\"] - base_elev) * np.maximum(0.0, 1.0 - (p[\"angle\"] - angle) / lw)\n    right_fall = base_elev + (p[\"elev\"] - base_elev) * np.maximum(0.0, 1.0 - (angle - p[\"angle\"]) / rw)\n    tent = np.where(angle <= p[\"angle\"], left_rise, right_fall)\n    skyline = np.maximum(skyline, tent)\n\n# Minor ridge bumps and rocky notches between major peaks\nminor_peaks = [\n    (9, 3440),\n    (33, 3490),\n    (56, 3520),\n    (73, 3460),\n    (88, 3540),\n    (108, 3500),\n    (138, 3470),\n    (162, 3510),\n    (198, 3490),\n    (224, 3530),\n    (258, 3450),\n    (280, 3480),\n]\nfor mp_angle, mp_elev in minor_peaks:\n    w = np.random.uniform(3, 7)\n    tent = base_elev + (mp_elev - base_elev) * np.maximum(0.0, 1.0 - np.abs(angle - mp_angle) / w)\n    skyline = np.maximum(skyline, tent)\n\n# Organic roughness — rocky jaggedness along ridges\nroughness = np.cumsum(np.random.randn(n_samples)) * 0.5\nroughness -= roughness.mean()\nskyline += roughness\nskyline = np.maximum(skyline, 2870.0)  # floor above y-axis lower limit\n\nskyline_df = pd.DataFrame({\"angle\": angle, \"elev\": skyline})\n\n# Stagger labels into three rows to avoid crowding for the 16 clustered peaks\nlabel_rows = [5650, 5350, 5050]\nmin_dx = 26\nplaced = []\nlabel_y_values = []\nfor _, p in peaks_df.iterrows():\n    chosen = label_rows[-1]\n    for ry in label_rows:\n        conflict = any(abs(p[\"angle\"] - pa) < min_dx and pr == ry for pa, pr in placed)\n        if not conflict:\n            chosen = ry\n            break\n    label_y_values.append(chosen)\n    placed.append((p[\"angle\"], chosen))\n\npeaks_df[\"label_y\"] = label_y_values\npeaks_df[\"elev_y\"] = peaks_df[\"label_y\"] - 130\nanchor_df = peaks_df[peaks_df[\"name\"] == \"Matterhorn\"]\nother_df = peaks_df[peaks_df[\"name\"] != \"Matterhorn\"]\n\n# Compass bearing labels for geographic orientation\ncompass_breaks = [22, 90, 160, 230, 280]\ncompass_labels = [\"WSW\", \"W\", \"NW\", \"N\", \"NE\"]\n\n# Title scaled for length (formula: round(16 * 67 / n), floor=11)\nTITLE = \"Wallis Panorama from Gornergrat · area-mountain-panorama · python · letsplot · anyplot.ai\"\ntitle_size = max(11, round(16 * 67 / len(TITLE)))\n\nplot = (\n    ggplot()\n    # Dark mountain silhouette with BRAND ridge outline — evening/dusk alpine feel\n    + geom_area(data=skyline_df, mapping=aes(x=\"angle\", y=\"elev\"), fill=MOUNTAIN_FILL, color=BRAND, size=0.8, alpha=1.0)\n    # Leader lines from each summit up to its label\n    + geom_segment(\n        data=peaks_df, mapping=aes(x=\"angle\", y=\"elev\", xend=\"angle\", yend=\"label_y\"), color=INK_SOFT, size=0.4\n    )\n    # Peak markers — hover tooltips are lets-plot's key interactive HTML differentiator\n    + geom_point(\n        data=peaks_df,\n        mapping=aes(x=\"angle\", y=\"elev\"),\n        color=BRAND,\n        size=2.5,\n        tooltips=layer_tooltips().line(\"@name\").line(\"@elev_text\"),\n    )\n    # Peak names — non-anchor summits\n    + geom_text(\n        data=other_df,\n        mapping=aes(x=\"angle\", y=\"label_y\", label=\"name\"),\n        size=3.5,\n        color=INK,\n        fontface=\"bold\",\n        vjust=0.0,\n    )\n    # Matterhorn anchor — slightly larger for focal emphasis\n    + geom_text(\n        data=anchor_df,\n        mapping=aes(x=\"angle\", y=\"label_y\", label=\"name\"),\n        size=4.5,\n        color=INK,\n        fontface=\"bold\",\n        vjust=0.0,\n    )\n    # Elevation sub-labels below each peak name\n    + geom_text(\n        data=peaks_df, mapping=aes(x=\"angle\", y=\"elev_y\", label=\"elev_text\"), size=3.0, color=INK_SOFT, vjust=0.0\n    )\n    + scale_x_continuous(name=\"Bearing\", breaks=compass_breaks, labels=compass_labels, limits=[0, 290], expand=[0, 0])\n    + scale_y_continuous(name=\"Elevation (m)\", breaks=[3000, 3500, 4000, 4500], limits=[2800, 6000], expand=[0, 0])\n    + labs(title=TITLE, subtitle=\"Pennine Alps 4000-m summits · 16 labeled peaks from Gornergrat (3089 m)\")\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=SKY_COLOR),\n        panel_grid_major_y=element_blank(),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        axis_line_x=element_line(color=INK_SOFT, size=0.5),\n        axis_line_y=element_blank(),\n        axis_ticks_x=element_line(color=INK_SOFT),\n        axis_ticks_y=element_blank(),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        plot_title=element_text(size=title_size, color=INK),\n        plot_subtitle=element_text(size=10, color=INK_MUTED),\n        plot_margin=[40, 40, 20, 20],\n    )\n)\n\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}