Skip to content

Commit b48a7b3

Browse files
feat(bokeh): implement contour-basic (#5330)
## Implementation: `contour-basic` - python/bokeh Implements the **python/bokeh** version of `contour-basic`. **File:** `plots/contour-basic/implementations/python/bokeh.py` **Parent Issue:** #855 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24867170776)* --------- 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 a8961a9 commit b48a7b3

2 files changed

Lines changed: 271 additions & 259 deletions

File tree

Lines changed: 91 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,110 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
contour-basic: Basic Contour Plot
3-
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: bokeh 3.9.0 | Python 3.14.4
4+
Quality: 90/100 | Updated: 2026-04-24
55
"""
66

7+
import os
8+
79
import numpy as np
8-
from bokeh.io import export_png, save
9-
from bokeh.models import BasicTicker, ColorBar, LinearColorMapper
10+
from bokeh.io import export_png, output_file, save
1011
from bokeh.palettes import Viridis256
1112
from bokeh.plotting import figure
12-
from bokeh.resources import CDN
13-
from contourpy import contour_generator
1413

1514

16-
# Data - create a 2D scalar field using a mathematical function
17-
np.random.seed(42)
18-
x = np.linspace(-3, 3, 50)
19-
y = np.linspace(-3, 3, 50)
15+
THEME = os.getenv("ANYPLOT_THEME", "light")
16+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
17+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
18+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
19+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
20+
21+
# Data — simulated topographic elevation map of a 10km x 10km mountain region
22+
x = np.linspace(0, 10, 90)
23+
y = np.linspace(0, 10, 90)
2024
X, Y = np.meshgrid(x, y)
2125

22-
# Create a Gaussian peak function with primary and secondary peaks
23-
# This shows contour features: nested rings, gradient directions, multiple maxima
24-
Z = np.exp(-(X**2 + Y**2)) + 0.5 * np.exp(-((X - 1.5) ** 2 + (Y + 1) ** 2) / 0.5)
25-
26-
# Define contour levels for smooth gradient visualization
27-
levels = np.linspace(Z.min(), Z.max(), 12)
28-
29-
# Map levels to colors from Viridis palette (colorblind-safe)
30-
n_levels = len(levels) - 1
31-
color_indices = [int(i * 255 / (n_levels - 1)) if n_levels > 1 else 0 for i in range(n_levels)]
32-
level_colors = [Viridis256[idx] for idx in color_indices]
33-
34-
# Generate filled contours using contourpy
35-
fill_xs = []
36-
fill_ys = []
37-
fill_colors = []
38-
39-
for i in range(len(levels) - 1):
40-
low_level = levels[i]
41-
high_level = levels[i + 1]
42-
43-
# Create filled contour generator
44-
cont_gen = contour_generator(x=x, y=y, z=Z, fill_type="ChunkCombinedOffset")
45-
filled = cont_gen.filled(low_level, high_level)
46-
47-
# ChunkCombinedOffset returns tuple of (points_list, offsets_list) per chunk
48-
points_list, offsets_list = filled
49-
50-
# Process each chunk
51-
for chunk_idx in range(len(points_list)):
52-
points = points_list[chunk_idx]
53-
offsets = offsets_list[chunk_idx]
54-
55-
if points is None or offsets is None:
56-
continue
57-
58-
# Extract polygons using offset pairs
59-
for j in range(len(offsets) - 1):
60-
start = int(offsets[j])
61-
end = int(offsets[j + 1])
62-
polygon = points[start:end]
63-
if len(polygon) > 2:
64-
fill_xs.append(polygon[:, 0].tolist())
65-
fill_ys.append(polygon[:, 1].tolist())
66-
fill_colors.append(level_colors[i])
67-
68-
# Generate contour lines using contourpy
69-
line_xs = []
70-
line_ys = []
71-
72-
cont_gen_lines = contour_generator(x=x, y=y, z=Z, line_type="SeparateCode")
73-
for level in levels:
74-
lines, codes = cont_gen_lines.lines(level)
75-
for line in lines:
76-
if len(line) > 1:
77-
line_xs.append(line[:, 0].tolist())
78-
line_ys.append(line[:, 1].tolist())
79-
80-
# Create Bokeh figure
26+
elevation = (
27+
850 * np.exp(-((X - 7) ** 2 + (Y - 7) ** 2) / 4.0)
28+
+ 550 * np.exp(-((X - 2.5) ** 2 + (Y - 3) ** 2) / 3.0)
29+
- 180 * np.exp(-((X - 5) ** 2 + (Y - 5) ** 2) / 8.0)
30+
+ 12 * X
31+
+ 350
32+
)
33+
34+
levels = np.linspace(elevation.min(), elevation.max(), 14)
35+
36+
# Plot
8137
p = figure(
8238
width=4800,
8339
height=2700,
84-
title="contour-basic · bokeh · pyplots.ai",
85-
x_axis_label="X Coordinate",
86-
y_axis_label="Y Coordinate",
40+
title="Mountain Terrain · contour-basic · bokeh · anyplot.ai",
41+
x_axis_label="Distance East (km)",
42+
y_axis_label="Distance North (km)",
8743
toolbar_location=None,
88-
tools="",
89-
x_range=(-3.2, 3.2),
90-
y_range=(-3.2, 3.2),
44+
x_range=(0, 10),
45+
y_range=(0, 10),
46+
match_aspect=True,
9147
)
9248

93-
# Plot filled contours
94-
if fill_xs:
95-
p.patches(xs=fill_xs, ys=fill_ys, fill_color=fill_colors, line_color=None, fill_alpha=0.9)
96-
97-
# Plot contour lines
98-
if line_xs:
99-
p.multi_line(xs=line_xs, ys=line_ys, line_color="#333333", line_width=1.5, line_alpha=0.7)
100-
101-
# Add color bar
102-
color_mapper = LinearColorMapper(palette=Viridis256, low=Z.min(), high=Z.max())
103-
color_bar = ColorBar(
104-
color_mapper=color_mapper,
105-
ticker=BasicTicker(desired_num_ticks=10),
106-
label_standoff=16,
107-
major_label_text_font_size="18pt",
49+
contour = p.contour(
50+
x=X, y=Y, z=elevation, levels=levels, fill_color=Viridis256, line_color=PAGE_BG, line_width=2, line_alpha=0.45
51+
)
52+
53+
colorbar = contour.construct_color_bar(
54+
title="Elevation (m)",
55+
title_text_font_size="26pt",
56+
title_text_color=INK,
57+
title_text_font_style="normal",
58+
title_standoff=20,
59+
major_label_text_font_size="22pt",
60+
major_label_text_color=INK_SOFT,
61+
background_fill_color=PAGE_BG,
10862
border_line_color=None,
109-
location=(0, 0),
110-
width=40,
111-
title="Z Value",
112-
title_text_font_size="20pt",
63+
width=60,
64+
padding=20,
11365
)
114-
p.add_layout(color_bar, "right")
115-
116-
# Styling for 4800x2700 px
117-
p.title.text_font_size = "28pt"
118-
p.xaxis.axis_label_text_font_size = "22pt"
119-
p.yaxis.axis_label_text_font_size = "22pt"
120-
p.xaxis.major_label_text_font_size = "18pt"
121-
p.yaxis.major_label_text_font_size = "18pt"
122-
123-
# Grid styling - subtle
124-
p.xgrid.grid_line_color = "#cccccc"
125-
p.ygrid.grid_line_color = "#cccccc"
126-
p.xgrid.grid_line_alpha = 0.3
127-
p.ygrid.grid_line_alpha = 0.3
128-
p.xgrid.grid_line_dash = [6, 4]
129-
p.ygrid.grid_line_dash = [6, 4]
130-
131-
# Axis styling
132-
p.axis.axis_line_color = "#666666"
133-
p.axis.major_tick_line_color = "#666666"
134-
135-
# Background
136-
p.background_fill_color = "#fafafa"
137-
p.border_fill_color = "white"
66+
p.add_layout(colorbar, "right")
67+
68+
# Typography — sized for 4800×2700 canvas
69+
p.title.text_font_size = "42pt"
70+
p.title.text_font_style = "bold"
71+
p.title.text_color = INK
72+
p.title.align = "center"
73+
74+
p.xaxis.axis_label_text_font_size = "32pt"
75+
p.yaxis.axis_label_text_font_size = "32pt"
76+
p.xaxis.axis_label_text_font_style = "normal"
77+
p.yaxis.axis_label_text_font_style = "normal"
78+
p.xaxis.major_label_text_font_size = "24pt"
79+
p.yaxis.major_label_text_font_size = "24pt"
80+
p.xaxis.axis_label_standoff = 28
81+
p.yaxis.axis_label_standoff = 28
82+
83+
# Theme-adaptive chrome
84+
p.background_fill_color = PAGE_BG
85+
p.border_fill_color = PAGE_BG
13886
p.outline_line_color = None
139-
140-
# Save PNG
141-
export_png(p, filename="plot.png")
142-
143-
# Save HTML for interactive version
144-
save(p, filename="plot.html", resources=CDN, title="contour-basic · bokeh · pyplots.ai")
87+
p.min_border_right = 60
88+
89+
p.xaxis.axis_label_text_color = INK
90+
p.yaxis.axis_label_text_color = INK
91+
p.xaxis.major_label_text_color = INK_SOFT
92+
p.yaxis.major_label_text_color = INK_SOFT
93+
p.xaxis.axis_line_color = INK_SOFT
94+
p.yaxis.axis_line_color = INK_SOFT
95+
p.xaxis.major_tick_line_color = INK_SOFT
96+
p.yaxis.major_tick_line_color = INK_SOFT
97+
p.xaxis.minor_tick_line_color = None
98+
p.yaxis.minor_tick_line_color = None
99+
100+
# Filled contour covers the plot area, so disable grid to avoid noise
101+
p.xgrid.grid_line_color = None
102+
p.ygrid.grid_line_color = None
103+
104+
p.xaxis.ticker.desired_num_ticks = 10
105+
p.yaxis.ticker.desired_num_ticks = 8
106+
107+
# Save
108+
export_png(p, filename=f"plot-{THEME}.png")
109+
output_file(f"plot-{THEME}.html", title="contour-basic · bokeh · anyplot.ai")
110+
save(p)

0 commit comments

Comments
 (0)