Terrain Elevation Profile Along Transect — Matplotlib

A cross-sectional line plot showing ground elevation along a path or transect line, with the area below the profile filled to create a terrain silhouette. Distance along the transect is plotted on the x-axis and elevation on the y-axis. This visualization is widely used in hiking and cycling route planning, civil engineering corridor design, and geomorphology to convey terrain shape and difficulty at a glance.

Terrain Elevation Profile Along Transect rendered with Matplotlib

Python source (Matplotlib)

""" anyplot.ai
area-elevation-profile: Terrain Elevation Profile Along Transect
Library: matplotlib 3.10.9 | Python 3.13.13
Quality: 90/100 | Updated: 2026-06-10
"""

import os
import sys


# Prevent this file from shadowing the installed matplotlib package on sys.path
_this_dir = os.path.dirname(os.path.abspath(__file__))
if _this_dir in sys.path:
    sys.path.remove(_this_dir)

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Data — Alpine hiking trail profile (120 km)
np.random.seed(42)
num_points = 480
distance = np.linspace(0, 120, num_points)

# Build realistic terrain with multiple peaks and valleys
base = 1200
elevation = base + np.zeros(num_points)
elevation += 600 * np.exp(-((distance - 25) ** 2) / 80)
elevation += 900 * np.exp(-((distance - 55) ** 2) / 120)
elevation += 450 * np.exp(-((distance - 85) ** 2) / 60)
elevation += 700 * np.exp(-((distance - 105) ** 2) / 90)
elevation += 200 * np.sin(distance * 0.15) * np.exp(-((distance - 40) ** 2) / 300)
elevation += 150 * np.cos(distance * 0.25)
elevation += np.cumsum(np.random.normal(0, 1.5, num_points))
elevation = np.maximum(elevation, 800)

# Landmarks
landmark_labels = ["Bergdorf", "Sonnalm", "Hoher Kamm", "Talbach", "Gipfelkreuz", "Seefeld Hut", "Felsgrat", "Alpstadt"]
landmark_distances = [0, 18, 28, 42, 55, 72, 88, 120]
landmark_elevations = [
    elevation[0],
    elevation[np.argmin(np.abs(distance - 18))],
    elevation[np.argmin(np.abs(distance - 28))],
    elevation[np.argmin(np.abs(distance - 42))],
    elevation[np.argmin(np.abs(distance - 55))],
    elevation[np.argmin(np.abs(distance - 72))],
    elevation[np.argmin(np.abs(distance - 88))],
    elevation[-1],
]
landmark_names = [
    f"{name}\n{int(round(elev))} m" for name, elev in zip(landmark_labels, landmark_elevations, strict=True)
]

# Plot
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)

y_min = 600
y_max = elevation.max()

# Gradient fill — terrain silhouette (imprint_seq: brand green → blue)
cmap = LinearSegmentedColormap.from_list("imprint_seq", ["#009E73", "#4467A3"])
gradient_resolution = 200
y_levels = np.linspace(y_min, y_max, gradient_resolution)
for i in range(len(y_levels) - 1):
    y_lo = y_levels[i]
    y_hi = y_levels[i + 1]
    clipped = np.clip(elevation, y_lo, y_hi)
    color = cmap(i / (len(y_levels) - 1))
    ax.fill_between(distance, y_lo, clipped, color=color, alpha=0.9, linewidth=0)

# Profile line — brand green primary data element
ax.plot(distance, elevation, color="#009E73", linewidth=2.0, zorder=3)

# Landmark annotations — stagger to separate the 18–28 km cluster
# Sonnalm pushed up high (horiz-centered) so it clears Hoher Kamm without going left into Bergdorf
label_configs = [
    ("left", (6, 12)),  # Bergdorf    (0 km)  — text right
    ("center", (0, 42)),  # Sonnalm     (18 km) — pushed up, clears Hoher Kamm below
    ("left", (6, 12)),  # Hoher Kamm  (28 km) — text right, at normal height
    ("center", (0, 12)),  # Talbach     (42 km)
    ("center", (0, 28)),  # Gipfelkreuz (55 km) — pushed up as focal peak
    ("center", (0, 12)),  # Seefeld Hut (72 km)
    ("center", (0, 12)),  # Felsgrat    (88 km)
    ("right", (-6, 12)),  # Alpstadt    (120 km) — text left
]
for (ha, (dx, dy)), name, d, e in zip(
    label_configs, landmark_names, landmark_distances, landmark_elevations, strict=True
):
    ax.plot([d, d], [y_min, e], color=INK_SOFT, linewidth=0.8, linestyle="--", alpha=0.4, zorder=2)
    ax.plot(d, e, "o", color=INK, markersize=5, zorder=4, markeredgecolor=PAGE_BG, markeredgewidth=1)
    ax.annotate(
        name,
        xy=(d, e),
        xytext=(dx, dy),
        textcoords="offset points",
        fontsize=9,
        fontweight="bold",
        ha=ha,
        va="bottom",
        color=INK,
    )

# Style
title = "Alpine Trail Profile · area-elevation-profile · python · matplotlib · anyplot.ai"
title_len = len(title)
title_fontsize = max(8, round(12 * 67 / title_len)) if title_len > 67 else 12
ax.set_xlabel("Distance (km)", fontsize=10, color=INK)
ax.set_ylabel("Elevation (m)", fontsize=10, color=INK)
ax.set_title(title, fontsize=title_fontsize, fontweight="medium", color=INK)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
for spine in ("left", "bottom"):
    ax.spines[spine].set_color(INK_SOFT)
ax.set_xlim(0, 120)
ax.set_ylim(y_min, y_max + 350)
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)

# Vertical exaggeration note
ax.text(
    0.99,
    0.02,
    "Vertical exaggeration ≈ 10×",
    transform=ax.transAxes,
    fontsize=8,
    ha="right",
    va="bottom",
    color=INK_MUTED,
    fontstyle="italic",
    bbox={"boxstyle": "round,pad=0.3", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.85},
)

fig.subplots_adjust(left=0.09, right=0.98, top=0.91, bottom=0.12)

# Save
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)

Part of Terrain Elevation Profile Along Transect on anyplot.ai.

Other implementations