Skip to content

Commit a37d2d6

Browse files
feat(bokeh): implement area-mountain-panorama (#5409)
## Implementation: `area-mountain-panorama` - python/bokeh Implements the **python/bokeh** version of `area-mountain-panorama`. **File:** `plots/area-mountain-panorama/implementations/python/bokeh.py` **Parent Issue:** #5365 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24941214203)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 048f38f commit a37d2d6

2 files changed

Lines changed: 437 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
""" anyplot.ai
2+
area-mountain-panorama: Mountain Panorama Profile with Labeled Peaks
3+
Library: bokeh 3.9.0 | Python 3.14.4
4+
Quality: 86/100 | Created: 2026-04-25
5+
"""
6+
7+
import os
8+
9+
import numpy as np
10+
from bokeh.io import export_png, output_file, save
11+
from bokeh.models import FixedTicker, Label
12+
from bokeh.plotting import figure
13+
14+
15+
# Theme tokens
16+
THEME = os.getenv("ANYPLOT_THEME", "light")
17+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
18+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
19+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
20+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
21+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
22+
BRAND = "#009E73" # Okabe-Ito position 1 — always first series
23+
24+
# Data — Wallis (Valais, Switzerland) summit panorama, ordered W → E
25+
peaks = [
26+
("Weisshorn", 8, 4506),
27+
("Zinalrothorn", 20, 4221),
28+
("Ober Gabelhorn", 31, 4063),
29+
("Dent Blanche", 42, 4358),
30+
("Matterhorn", 58, 4478),
31+
("Breithorn", 72, 4164),
32+
("Pollux", 81, 4092),
33+
("Castor", 89, 4223),
34+
("Liskamm", 97, 4527),
35+
("Dufourspitze", 109, 4634),
36+
("Strahlhorn", 121, 4190),
37+
("Rimpfischhorn", 132, 4199),
38+
("Allalinhorn", 142, 4027),
39+
("Alphubel", 152, 4206),
40+
("Täschhorn", 162, 4491),
41+
("Dom", 174, 4545),
42+
]
43+
44+
# Build ridgeline control points: peaks alternating with saddles (cols)
45+
np.random.seed(42)
46+
ctrl_x = [-3.0]
47+
ctrl_y = [3250.0]
48+
for i, (_, ang, el) in enumerate(peaks):
49+
ctrl_x.append(float(ang))
50+
ctrl_y.append(float(el))
51+
if i < len(peaks) - 1:
52+
next_ang = peaks[i + 1][1]
53+
next_el = peaks[i + 1][2]
54+
col_ang = (ang + next_ang) / 2 + np.random.uniform(-1.2, 1.2)
55+
col_drop = np.random.uniform(420, 820)
56+
col_el = min(el, next_el) - col_drop
57+
ctrl_x.append(float(col_ang))
58+
ctrl_y.append(float(col_el))
59+
ctrl_x.append(184.0)
60+
ctrl_y.append(3350.0)
61+
ctrl_x = np.array(ctrl_x)
62+
ctrl_y = np.array(ctrl_y)
63+
64+
# Smooth ridgeline via cosine smoothstep between adjacent control points
65+
ridge_x = []
66+
ridge_y = []
67+
for i in range(len(ctrl_x) - 1):
68+
n = 80
69+
last = i == len(ctrl_x) - 2
70+
t = np.linspace(0.0, 1.0, n, endpoint=last)
71+
s = 0.5 - 0.5 * np.cos(np.pi * t)
72+
ridge_x.append(ctrl_x[i] + (ctrl_x[i + 1] - ctrl_x[i]) * t)
73+
ridge_y.append(ctrl_y[i] + (ctrl_y[i + 1] - ctrl_y[i]) * s)
74+
ridge_x = np.concatenate(ridge_x)
75+
ridge_y = np.concatenate(ridge_y)
76+
77+
# Anchor the silhouette polygon at the lower edge of the visible y-range
78+
Y_FLOOR = 2500
79+
poly_x = np.concatenate([[ridge_x[0]], ridge_x, [ridge_x[-1]]])
80+
poly_y = np.concatenate([[Y_FLOOR], ridge_y, [Y_FLOOR]])
81+
82+
# Plot
83+
p = figure(
84+
width=4800,
85+
height=2700,
86+
title="Wallis 4000ers · area-mountain-panorama · bokeh · anyplot.ai",
87+
y_axis_label="Elevation (m)",
88+
x_range=(-3, 184),
89+
y_range=(Y_FLOOR, 5400),
90+
background_fill_color=PAGE_BG,
91+
border_fill_color=PAGE_BG,
92+
toolbar_location=None,
93+
)
94+
95+
# Mountain silhouette (first categorical series — brand green)
96+
p.patch(poly_x, poly_y, fill_color=BRAND, line_color=BRAND, line_width=2)
97+
98+
# Peak labels staggered across three vertical tiers, with thin leader lines + summit dots
99+
LEVEL_TIERS = [4880, 5040, 5200]
100+
labels = []
101+
for i, (name, ang, el) in enumerate(peaks):
102+
label_y = LEVEL_TIERS[i % 3]
103+
is_focal = name == "Matterhorn"
104+
leader_color = INK if is_focal else INK_SOFT
105+
leader_alpha = 0.9 if is_focal else 0.55
106+
leader_width = 3.0 if is_focal else 1.8
107+
108+
# Leader line from just above summit to just below the label block
109+
p.line(
110+
[ang, ang], [el + 25, label_y - 90], line_color=leader_color, line_alpha=leader_alpha, line_width=leader_width
111+
)
112+
113+
# Summit dot — slightly larger and inked for the focal peak
114+
p.scatter(
115+
[ang],
116+
[el],
117+
size=26 if is_focal else 16,
118+
fill_color=INK if is_focal else INK_SOFT,
119+
line_color=PAGE_BG,
120+
line_width=2,
121+
)
122+
123+
# Name (top of stacked label block)
124+
labels.append(
125+
Label(
126+
x=ang,
127+
y=label_y + 55,
128+
text=name,
129+
text_color=INK,
130+
text_font_size="24pt" if is_focal else "20pt",
131+
text_font_style="bold" if is_focal else "normal",
132+
text_align="center",
133+
text_baseline="bottom",
134+
)
135+
)
136+
137+
# Elevation (bottom of stacked label block)
138+
labels.append(
139+
Label(
140+
x=ang,
141+
y=label_y + 30,
142+
text=f"{el:,} m",
143+
text_color=INK_SOFT,
144+
text_font_size="18pt",
145+
text_align="center",
146+
text_baseline="top",
147+
)
148+
)
149+
150+
for lbl in labels:
151+
p.add_layout(lbl)
152+
153+
# Typography
154+
p.title.text_font_size = "36pt"
155+
p.title.text_color = INK
156+
p.title.text_font_style = "normal"
157+
p.title.align = "center"
158+
159+
p.yaxis.axis_label_text_font_size = "24pt"
160+
p.yaxis.axis_label_text_color = INK
161+
p.yaxis.major_label_text_font_size = "20pt"
162+
p.yaxis.major_label_text_color = INK_SOFT
163+
p.yaxis.axis_line_color = INK_SOFT
164+
p.yaxis.major_tick_line_color = INK_SOFT
165+
p.yaxis.minor_tick_line_color = None
166+
p.yaxis.ticker = FixedTicker(ticks=[2500, 3000, 3500, 4000, 4500, 5000])
167+
p.yaxis.axis_label_standoff = 18
168+
169+
# Hide x-axis entirely — the panorama silhouette is the visual; bearings would clutter it
170+
p.xaxis.visible = False
171+
172+
# Grid: y-only, very subtle
173+
p.outline_line_color = None
174+
p.xgrid.grid_line_color = None
175+
p.ygrid.grid_line_color = INK
176+
p.ygrid.grid_line_alpha = 0.10
177+
178+
# Save
179+
export_png(p, filename=f"plot-{THEME}.png")
180+
output_file(f"plot-{THEME}.html", title="Wallis 4000ers · area-mountain-panorama · bokeh · anyplot.ai")
181+
save(p)

0 commit comments

Comments
 (0)