|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | 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 |
10 | 11 | from bokeh.palettes import Viridis256 |
11 | 12 | from bokeh.plotting import figure |
12 | | -from bokeh.resources import CDN |
13 | | -from contourpy import contour_generator |
14 | 13 |
|
15 | 14 |
|
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) |
20 | 24 | X, Y = np.meshgrid(x, y) |
21 | 25 |
|
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 |
81 | 37 | p = figure( |
82 | 38 | width=4800, |
83 | 39 | 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)", |
87 | 43 | 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, |
91 | 47 | ) |
92 | 48 |
|
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, |
108 | 62 | 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, |
113 | 65 | ) |
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 |
138 | 86 | 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