Mountain Panorama Profile with Labeled Peaks — plotnine

A panoramic mountain silhouette chart that renders the horizon as seen from a fixed vantage point, like a photograph of a ridgeline against the sky. A filled area under the skyline curve traces the ridgeline across a horizontal viewing range (in degrees of bearing or horizontal distance), and major summits are annotated with their name and elevation. The skyline is jagged and angular — sharp triangular peaks with steep, often asymmetric flanks meeting at pointed apexes, connected by rugged ridges with cols, sub-peaks and rocky notches — not a sequence of smooth bell-shaped bumps. Unlike an elevation-profile-along-a-trail, this plot is the angular view of the surrounding peaks from a single observer, making it ideal for summit-identification infographics, alpine panoramas, and travel guides.

Mountain Panorama Profile with Labeled Peaks rendered with plotnine

Python source (plotnine)

""" anyplot.ai
area-mountain-panorama: Mountain Panorama Profile with Labeled Peaks
Library: plotnine 0.15.7 | Python 3.13.14
Quality: 87/100 | Updated: 2026-06-30
"""

import os

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    coord_cartesian,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_line,
    geom_point,
    geom_ribbon,
    geom_segment,
    geom_text,
    ggplot,
    labs,
    scale_x_continuous,
    scale_y_continuous,
    theme,
    theme_minimal,
)


# Imprint palette / theme-adaptive chrome tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
BRAND = "#009E73"  # Imprint palette position 1 — peak markers (data elements)
MOUNTAIN = "#263040"  # Dark slate — spec requires dark solid fill for dusk/photo-like feel

# Wallis (Valais) panorama from Gornergrat vantage
peaks = (
    pd.DataFrame(
        {
            "name": [
                "Weisshorn",
                "Zinalrothorn",
                "Ober Gabelhorn",
                "Dent Blanche",
                "Matterhorn",
                "Liskamm",
                "Castor",
                "Pollux",
                "Breithorn",
                "Monte Rosa",
                "Strahlhorn",
                "Rimpfischhorn",
                "Allalinhorn",
                "Alphubel",
                "Täschhorn",
                "Dom",
            ],
            "angle_deg": [4, 12, 19, 26, 38, 60, 67, 72, 78, 90, 105, 113, 122, 130, 137, 144],
            "elevation_m": [
                4506,
                4221,
                4063,
                4358,
                4478,
                4527,
                4223,
                4092,
                4164,
                4634,
                4190,
                4199,
                4027,
                4206,
                4491,
                4545,
            ],
        }
    )
    .sort_values("angle_deg")
    .reset_index(drop=True)
)

FLOOR = 2500

# Piecewise-linear tent/triangle functions — spec forbids Gaussian bumps;
# each peak contributes a sharp triangular profile with asymmetric linear flanks
np.random.seed(42)
n_samples = 1600
angles = np.linspace(-3, 152, n_samples)

# Undulating base ridge (valley floors and connecting ridges between peaks)
base_ridge = 2750 + 80 * np.sin(angles / 14) + 55 * np.sin(angles / 4.5 + 0.7)
elevation = base_ridge.copy()

for _, p in peaks.iterrows():
    # Per-peak seed for reproducible asymmetric flank widths independent of iteration order
    rng = np.random.RandomState(int(p["angle_deg"] * 17 + 3))
    left_w = rng.uniform(6, 14)  # degrees from apex to left base
    right_w = rng.uniform(5, 11)  # degrees from apex to right base (asymmetric)
    peak_h = p["elevation_m"] - FLOOR

    dist = angles - p["angle_deg"]
    tent = np.where(
        dist < 0,
        np.maximum(0.0, 1.0 + dist / left_w),  # linear rise on left flank
        np.maximum(0.0, 1.0 - dist / right_w),  # linear fall on right flank
    )
    elevation = np.maximum(elevation, FLOOR + peak_h * tent)

# Window=3 only: preserves rocky jaggedness while removing single-point spikes
ridge_noise = np.random.normal(0, 28, n_samples)
elevation = elevation + ridge_noise
elevation = pd.Series(elevation).rolling(window=3, center=True, min_periods=1).mean().values
elevation = np.maximum(elevation, FLOOR)

skyline = pd.DataFrame({"angle_deg": angles, "elevation_m": elevation, "y_floor": FLOOR})

# 3-row stagger — 3 vertical tiers break up the dense 0–78° cluster
row_map = {
    "Weisshorn": 0,
    "Zinalrothorn": 1,
    "Ober Gabelhorn": 2,
    "Dent Blanche": 0,
    "Matterhorn": 1,
    "Liskamm": 0,
    "Castor": 2,
    "Pollux": 1,
    "Breithorn": 0,
    "Monte Rosa": 2,
    "Strahlhorn": 1,
    "Rimpfischhorn": 0,
    "Allalinhorn": 2,
    "Alphubel": 1,
    "Täschhorn": 0,
    "Dom": 2,
}
row_y = {0: 5050, 1: 5250, 2: 5450}
peaks["row"] = peaks["name"].map(row_map)
peaks["label_y"] = peaks["row"].map(row_y)
peaks["leader_top"] = peaks["label_y"] - 80
peaks["label"] = peaks.apply(lambda r: f"{r['name']}\n{int(r['elevation_m']):,} m", axis=1)
peaks["is_anchor"] = peaks["name"] == "Matterhorn"

others = peaks[~peaks["is_anchor"]]
anchor = peaks[peaks["is_anchor"]]

plot = (
    ggplot()
    # Dark silhouette fill — photo-like, evening/dusk alpine feel
    + geom_ribbon(aes(x="angle_deg", ymin="y_floor", ymax="elevation_m"), data=skyline, fill=MOUNTAIN, alpha=1.0)
    # Ridgeline outline for crispness at the sky-mountain boundary
    + geom_line(aes(x="angle_deg", y="elevation_m"), data=skyline, color=INK_SOFT, size=0.4, alpha=0.35)
    # Leader lines from summit up to label tier
    + geom_segment(
        aes(x="angle_deg", xend="angle_deg", y="elevation_m", yend="leader_top"),
        data=others,
        color=INK_MUTED,
        size=0.35,
    )
    + geom_segment(
        aes(x="angle_deg", xend="angle_deg", y="elevation_m", yend="leader_top"), data=anchor, color=INK, size=0.65
    )
    # Summit markers — brand green data elements marking each labeled peak
    + geom_point(
        aes(x="angle_deg", y="elevation_m"), data=others, size=2.0, color=PAGE_BG, fill=BRAND, stroke=0.5, shape="o"
    )
    + geom_point(
        aes(x="angle_deg", y="elevation_m"), data=anchor, size=3.2, color=PAGE_BG, fill=BRAND, stroke=0.9, shape="o"
    )
    # Peak labels — Matterhorn bold as focal summit
    + geom_text(
        aes(x="angle_deg", y="label_y", label="label"), data=others, size=3.0, color=INK, ha="center", va="bottom"
    )
    + geom_text(
        aes(x="angle_deg", y="label_y", label="label"),
        data=anchor,
        size=3.8,
        color=INK,
        ha="center",
        va="bottom",
        fontweight="bold",
    )
    + scale_x_continuous(expand=(0.005, 0))
    + scale_y_continuous(
        breaks=[2500, 3000, 3500, 4000, 4500, 5000], labels=["2,500", "3,000", "3,500", "4,000", "4,500", "5,000"]
    )
    + coord_cartesian(xlim=(-2, 151), ylim=(2500, 5700))
    + labs(
        x="",
        y="Elevation (m)",
        title="Wallis from Gornergrat · area-mountain-panorama · python · plotnine · anyplot.ai",
    )
    + theme_minimal()
    + theme(
        figure_size=(8, 4.5),
        text=element_text(size=7, color=INK_SOFT),
        plot_title=element_text(size=11, color=INK, ha="left", margin={"b": 8}),
        axis_title_y=element_text(size=10, color=INK, margin={"r": 8}),
        axis_text_y=element_text(size=8, color=INK_SOFT),
        axis_text_x=element_blank(),
        axis_ticks=element_blank(),
        axis_line=element_blank(),
        panel_grid_major_x=element_blank(),
        panel_grid_major_y=element_line(color=INK_SOFT, size=0.25, alpha=0.12),
        panel_grid_minor=element_blank(),
        panel_border=element_blank(),
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        plot_margin=0.03,
    )
)

plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False)

Part of Mountain Panorama Profile with Labeled Peaks on anyplot.ai.

Other implementations